一、组件介绍
官网链接:Collapse 折叠面板 | Element (gitee.io)
collapse折叠面板组件可以折叠/展开内容区域,通常与collapse-item组件组合使用。
1.1 collapse 属性
- v-model/model-value: string/array类型;指定当前激活的面板,当为手风琴模式时, 值的类型必须是string;
- accordion: boolean类型;是否手风琴模式(一次只能展开一个面板),默认false;
1.2 collapse-item 属性
- name: string/number类型,唯一标识符,如果不传入,会随机生成一个四位数字作为name;
- title:string类型,面板标题;
- disabled: boolean类型,是否禁用,默认false;
1.3 collapse-item 具名插槽
- title: 可以使用具名插槽自定义标题
二、源码分析
2.1 collapse组件源码
<template>
<div class="el-collapse" role="tablist" aria-multiselectable="true">
<slot></slot>
</div>
</template>
<script lang="ts">
// mitt是一个事件总线库,提供on/emit/off等方式进行事件监听/发射等功能
import mitt, { Emitter } from 'mitt'
setup(props, { emit }) {
// modelValue可能是string也可能是array,通过[].concat将其装换成array类型
const activeNames = ref([].concat(props.modelValue))
// 生成一个事件总线
const collapseMitt: Emitter = mitt()
// 设置激活的面板,参数是面板的name
const setActiveNames = _activeNames => {
// 转换成数组
activeNames.value = [].concat(_activeNames)
// 手风琴模式下,只取第一个元素
const value = props.accordion ? activeNames.value[0] : activeNames.value
// 向上发射v-model事件
emit(UPDATE_MODEL_EVENT, value)
// 向上发射change事件
emit(CHANGE_EVENT, value)
}
// item点击事件
const handleItemClick = name => {
if (props.accordion) {
// 手风琴模式
setActiveNames(
(activeNames.value[0] || activeNames.value[0] === 0) &&
activeNames.value[0] === name
? ''
: name,
)
} else {
const _activeNames = activeNames.value.slice(0)
const index = _activeNames.indexOf(name)
// 当前item已经展开,此次点击则是关闭操作
if (index > -1) {
_activeNames.splice(index, 1)
} else {
// 当前item未展开,此次点击是打开操作
_activeNames.push(name)
}
// 调用setActiveNames
setActiveNames(_activeNames)
}
}
// 监听 modelValue 的值
watch(
() => props.modelValue,
() => {
activeNames.value = [].concat(props.modelValue)
},
)
// mitt事件总线监听item-click,使用handleItemClick作为处理函数
collapseMitt.on('item-click', handleItemClick)
onUnmounted(() => {
// 卸载时清除所有事件监听,防止内存泄漏
collapseMitt.all.clear()
})
// 向子组件提供数据
provide('collapse', {
activeNames,
collapseMitt,
})
return {
activeNames,
setActiveNames,
handleItemClick,
}
},
})
</script>
总结:collapse组件代码比较简单,template部分提供默认插槽,script部分负责维护激活面板等数据,使用mitt事件总线与子组件通信,监听item-click事件。
2.2 collapse-item 组件
template
<template>
<div
class="el-collapse-item"
:class="{'is-active': isActive, 'is-disabled': disabled }"
>
// 标题部分
<div
role="tab"
:aria-expanded="isActive"
:aria-controls="`el-collapse-content-${id}`"
:aria-describedby="`el-collapse-content-${id}`"
>
<div
:id="`el-collapse-head-${id}`"
class="el-collapse-item__header"
role="button"
:tabindex="disabled ? -1 : 0"
:class="{
'focusing': focusing,
'is-active': isActive
}"
@click="handleHeaderClick"
@keyup.space.enter.stop="handleEnterClick"
@focus="handleFocus"
@blur="focusing = false"
>
// slot具名插槽
<slot name="title">{{ title }}</slot>
// 展开/收起箭头
<i
class="el-collapse-item__arrow el-icon-arrow-right"
:class="{'is-active': isActive}"
>
</i>
</div>
</div>
// el-collapse-transition是一个展示过渡效果的组件
// 其内部使用vue官方的transition组件,并绑定enter/leave等钩子函数,实现动画效果
<el-collapse-transition>
// 内容部分
<div
v-show="isActive"
:id="`el-collapse-content-${id}`"
class="el-collapse-item__wrap"
role="tabpanel"
:aria-hidden="!isActive"
:aria-labelledby="`el-collapse-head-${id}`"
>
<div class="el-collapse-item__content">
<slot></slot>
</div>
</div>
</el-collapse-transition>
</div>
</template>
script部分
setup(props) {
// inject注入父组件提供的数据
const collapse = inject<CollapseProvider>('collapse')
// 使用父组件提供的mitt事件总线
const collapseMitt = collapse?.collapseMitt
const contentWrapStyle = ref({
height: 'auto',
display: 'block',
})
const contentHeight = ref(0)
const focusing = ref(false)
const isClick = ref(false)
// 生成随机Id
const id = ref(generateId())
// 动态计算,父组件的activeNames是否包含当前name
const isActive = computed(() => {
return collapse?.activeNames.value.indexOf(props.name) > -1
})
const handleFocus = () => {
setTimeout(() => {
if(!isClick.value) {
focusing.value = true
} else {
isClick.value = false
}
}, 50)
}
// 点击标题事件
const handleHeaderClick = () => {
if(props.disabled) return
// 通过mitt事件总线,发送item-click事件,参数是当前面板的name
collapseMitt?.emit('item-click', props.name)
focusing.value = false
isClick.value = true
}
// enter按键事件,也是发送item-click事件
const handleEnterClick = () => {
collapseMitt?.emit('item-click', props.name)
}
return {
isActive,
contentWrapStyle,
contentHeight,
focusing,
isClick,
id,
handleFocus,
handleHeaderClick,
handleEnterClick,
collapse,
}
},
2.3 总结
mitt是一个事件总线库,提供on/emit/off/clear等方法;- 使用transition展示过渡效果,过渡效果可以通过js或css控制;