本文是 Element 的组件源码学习系列。
项目源码:ElemeFE/element | GitHub,Tag:v2.13.0
Collapse 组件使用文档:Tag 标签
.vue 文件:/packages/collapse
.scss 文件:/packages/theme-chalk/collapse.scss
.d.ts 文件:/types/collapse.d.ts
折叠面板,是前端入门的同学,经常手写的入门 Demo,我们现在也来学习一下,Element 是怎么做折叠面板这个组件的实现的。
Props
一样的,咱们先来了解一下 el-collapse
组件的入参,看看用户是如何使用这个组件的:
1 | props: { |
accordion
属性,表示的是,当前折叠面板,是否使用手风琴模式。
value
属性,当前激活的面板(如果是手风琴模式,绑定值类型需要为 string
,否则为array
)。表示的是用户页面上,被激活的面板是哪一个。手风琴模式,只有一个唯一的被激活面板,所以绑定值类型就是一个 string
了。
但是,我们并不是直接给 value
属性赋值,而是使用的 v-model
来利用 value
属性。组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件
。
我们来看一下具体实现:
1 | // collapse.vue |
首先,当我们给 value
属性赋值,或者给 v-model
赋值的时候,都会改变 activeNames
的值。这里比较巧妙的是使用 Array.prototype.concat
方法,因为 value
的类型可能是一个数组,也可能是一个元素,所以直接用 concat
方法统一处理成一个元素。
首先,用户在点击折叠面板的时候,点击的是里层 el-collapse-item
元素,所以我们来看一下,用户在点击打开或关闭一个面板,会有什么相关的逻辑:
1 | // collpase-item.vue |
当这个组件没有设置不可点击的时候,首先会通过 this.dispatch
方法,给父 ElCollapse
组件发出一个 item-click
事件。然后设置两个属性的值。
等等,this.dispatch
是个啥?好像不是原生的 dispatchEvent
属性啊,但是看起来差不多。也没有见到 Vue 提供 dispatch
方法呀。最后,我们在 src/mixins/emitter.js 中找到了该方法的实现,原来是是注册到全局的一个混入方法。我们来看一下具体的实现:
1 | // src/mixins/emitter.js |
其本质方法,就是通过 this.$parent
,层层向上,找对应的组件,然后调用该组件的 $emit
方法,产生一个事件。
在咱们这里,是找到祖先的 ElCollapse
组件,因为 Element 的推荐使用方案里面,el-collapse-item
组件是要被包裹在 el-collpase
组件内部的。然后产生一个 item-click
事件。我们来看看 item-click
事件做了什么:
1 | // collpase.vue |
在 el-collpase
的 created
方法中,设置了组件需要监听 item-click
事件,并且调用 handleItemClick
方法。
handleItemClick
方法,主要是处理 el-collpase-item
事件的入参。大致的逻辑是,将 this.activeNames
设置为正确的值。什么是正确的值呢?
- 对于手风琴模式:如果当前 item 已经是
this.activeNames[0]
(说明已经打开了),则设置为''
(关上),否则将this.activeNames[0]
设置为被点击 item(打开)。 - 对于非手风琴模式:如果当前 item 已经在
this.activeNames
数组中存在了(说明已经打开了),则从数组中过滤(关上);否则添加到数组中(打开)
最后再触发 input
事件 和 change
事件。input
事件是 v-model
要求触发的事件;change
事件是 el-collapse
对外提供的监听事件。
所以,我们对于 el-collpase
组件负责的事情,有一个总结:
- 维护一个折叠面板的总体面板开关状态
- 监听每一个面板的点击事件,计算出最新的总体面板开关状态
- 提供对外的
change
事件
而每个面板 el-collpase-item
,是否显示,在面板组件中自己实现。
el-collpase-item 控制显示效果
下面,我们来看看,el-collpase-item
如何控制自己是否要显示的:
1 | // el-collpase-item |
每一个 el-collpase-item
,都会注入父 el-collpase
组件,然后监听 this.collapse.activeNames
数组,来判断自己是否要显示。所有的 el-collpase-item
组件,都需要提供一个唯一的 name
属性,来表明自己是谁,这样才能计算出哪个面板需要展开。如果没有提供 name
属性,Element 会使用 this._uid
属性来表明每一个组件。this._uid
是 Vue 的一个内置属性,每个 Vue 实例都会有一个递增的id。
现在,el-collpase-item
已经有了 isActive
属性来表示该组件是否应该展示了,下面来看看展示、消失的效果是如何实现的:
1 | // el-collpase-item 的 <template> |
整个 el-collpase-item
主要分两部分,第一部分是 Header,第二部分是会被折叠的主要内容。
Header 部分我就不仔细介绍了,主要是使用 flex 布局,然后最右边的箭头初始化的时候是一个向右的箭头,如果有 is-active
类,箭头就会通过 CSS 旋转能力转为向下 transform: rotate(90deg);
Content 部分,要展示的内容,通过一个 <slot>
被包裹在一个 el-collapse-transition
中。这个 el-collapse-transition
是什么呢?我们可以在 src/transition/collapse-transition.js 中找到。具体的实现多,但主要是实现各个动画接口的效果。所有的动画接口,是复用的 Vue transition
组件的接口,参考 过渡 & 动画 | JavaScript 钩子,也不一一细讲了,