本文是 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 钩子,也不一一细讲了,