概述
树
即为无限层级,无限节点。大致应用场景为:文件夹目录,组织架构等带有明显层级关系的业务场景。本文说明了一些自己在实现树组件的一些思考和优化(jsx
)。
// 树形数据
const treeData = [
{
name: '一级 1',
id: 1,
children: [
{
name: '二级 1-1',
id: 2,
children: [
{
name: '三级 1-1-1',
id: 3,
children: [
{
name: '四级 1-1-1-1',
id: 4,
children: [],
checked: true,
disabled: true
},
{
name: '四级 1-1-1-2',
id: 5,
children: [],
checked: false
}
]
}
]
},
{
name: '二级 1-2',
id: 8,
children: [
{
name: '三级 1-2-1',
id: 9,
children: [
{
name: '四级 1-2-1-1',
id: 10,
children: []
}
]
}
]
}
]
},
{
name: '一级 2',
id: 11,
children: [
{
name: '二级 2-1',
id: 12,
children: []
},
{
name: '二级 2-2',
id: 15,
children: []
}
]
}
]
设计
每一个节点都可以认为是拥有同样的功能,即 每一个节点都是在渲染相同的组件。从这一点触发,树组件拆分为两个组件,一个是Tree
入口组件,一个是Node
叶子组件。
Tree
组件负责处理公共数据、方法,以及初始化时组件需要内置的一些数据。并向外暴露标准APINode
组件负责渲染真实的节点,并且递归渲染自身。
优化
- 在入口组件初始化时,遍历整个树形数据,为每一个子节点绑定其父节点引用。目的是为了在更新节点选中状态时,获取当前引用,反向遍历更新节点状态
// Tree.vue 初始化数据
initData (arr) {
console.time('init-Data')
if (this.showCheckbox) {
arr.forEach(node => {
const stack = [node]
while (stack.length !== 0) {
const item = stack.pop()
// 选中节点
if (item.checked) this.commonData.checkedNode.push(item)
// 包含子节点
if (this.hasChild(item)) {
item[this.childKey].forEach(child => {
child._parent = item._parent ? [...item._parent, item] : [item]
stack.push(child)
})
}
// 半选按钮
if (stack.length === 0) this.resetState(item)
}
})
}
console.timeEnd('init-Data')
// console.log(arr)
this.nodeData = arr
}
resetState (node) {
/**
* 思考:treeData,父节点的半选状态需要根据子节点的状态去展现,
* 有什么思路可以最快确定所有节点自身的半选状态。
* 1. treeData无限级嵌套
* 2. treeData依赖变量x的展现,所以一次递归几乎不可能完成。
* 3. 如何避免循环更新节点状态导致的性能浪费。
* 思路:
* 1. 递归当前Object,找出层级最高的一个节点,并且每个节点保存其父节点引用
* 2. 根据父节点引用进行反向查找,从而确定每个父级点的状态
* 3. 一级节点父节点即自身
*/
// const length = node._parent?.length || 0
const length = node._parent ? node._parent.length : 0
for (let i = length - 1; i >= 0; i--) {
let halfelEction = false
let cur = node._parent[i]
let checked = cur.checked
cur[this.childKey].forEach(child => {
if (child.checked || child._halfelEction) halfelEction = true
if (!child.checked) checked = false
})
this.$set(cur, 'checked', checked)
this.$set(cur, '_halfelEction', halfelEction)
!checked
? this.commonData.checkedNode = this.commonData.checkedNode.filter(cNode => cNode[this.nodeKey] !== cur[this.nodeKey])
: this.commonData.checkedNode.push(cur)
}
}
- 子组件状态变更时,根据变更的状态以及节点自身的属性(是否是父级节点)触发不同的事件,做到父节点向下捕获,子节点向上冒泡
// Node.js
checked (val) {
/**
* 根据自身的状态不同,向上 && 向下发送事件不同,根据事件类别,减少计算量(考虑子节点含有禁用)
* 0. 自身是半选 ---- 被动
* a: 子级更新,才会导致父级变更为半选的状态
* 1. 自身是选中 ---- 主动
* a: 根据平级所有节点选中状态,判断是否向上发送被动选中事件
* b: 向下发送被动选中事件
* 2. 自身是选中 ---- 被动
* a: 重置半选状态
* 3. 自身是取消 ---- 主动
* a: 向上发送半选事件
* 4. 自身是取消 ---- 被动
* a: 重置半选状态
*/
// !val
// ? this.commonData.checkedNode = this.commonData.checkedNode.filter(cNode => cNode[this.nodeKey] !== this.node[this.nodeKey])
// : this.commonData.checkedNode.push(this.node)
// this.node._halfelEction && this.$set(this.node, '_halfelEction', false)
/**
* 是否是末级节点
* 1. 是:
* a:直接向上冒泡
* b:逐层更新父级节点状态
* 2. 否:
* a:传递捕获找到最末级节点根据此次状态,被动更新末级所有节点状态,
* b:逐层更新父级节点状态
*/
if (this.hasChild(this.node)) {
this.parentChecked(val)
} else {
this.emitEvent('on-child-checked')
}
this.emitEvent('on-checked', this.node, this.index)
}
拓展
- 使用
v-if
去渲染子组件,懒加载 - 懒加载时组件的状态怎么同步,何时同步,怎么优化同步?
链接
optimization-ui
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!