概述:对Tree的懒加载与非懒加载进行分析,进行某个树节点的刷新,在单选、多选下对树进行初始化反选并展开选择节点等。
一、对Tree是否懒加载说明:
1.懒加载:Tree的懒加载,用一个属性控制:lazy。使用lazy,就要使用load来加载数据进行渲染树。
1-1.原理:初始化触发load函数先加载初始数据,通过点击某个节点,触发load函数,来加载这个节点下的子节点。
1-2.比较适合初始化没有反选的情况(这里只是说不适合,并不是不能做,比如初始化的时候,一次性加载到有选项的节点(需要按照层级包裹的形式),进行反选);适合数据量比较大的时候,对部分节点刷新也比较友好。
2.非懒加载:非懒加载,即一次性加载所有的数据展示出来,使用data存放所有的数据。
2-1.原理:按照层级包裹的形式整理数据进行展示。
2-2.适合数据量小的时候;比较适合初始化时有选中项、需要展开某些节点的情况。
二、详细操作(使用前请对Tree的各种属性有所了解):
tree引用——通用代码:通过属性控制是否懒加载(注:无论是否懒加载,应一种方式走完全流程,中间不可来回切换)
<template>
<el-tree
ref="treeRef"
:lazy="lazy"
:data="treeList"
:show-checkbox="isShowCheckBox"
:node-key="nodeKey"
:load="loadTreeNode"
:props="props"
:default-checked-keys="defaultChecks"
:default-expanded-keys="defaultExpands"
highlight-current
@node-click="menuNodeClick"
@check="checkChange">
</el-tree>
</template>
<script>
export default {
name: 'tree',
data() {
return {
lazy: true,
treeList: [],
isShowCheckBox: false,
nodeKey: 'id',
props: {
check: 'isCheck',
children: 'children',
isLeaf: 'isFinal',
label: 'name'
},
selectNodeData: null,
selectNode: null
}
},
methods: {
/**
* 公共函数——调用后台请求数据
* @node[node]: 要加载的子节点的节点数据
* @resolve: 用于resolve数据
*/
requestTreeNode(node, resolve) {
this.axios(...).then(res => {
if(resolve) { // 判断是否是懒加载过来的请求
resolve(res.data)
}else if(node) { // 如果有node参数,则更新node
node.doCreateChildren(res.data) // 将数据放入node里创建树
this.$nextTick(() => {
// 设置当前选择的节点
this.$refs.tree.setCurrentKey(
this.selectNodeData[this.nodeKey]
)
// 将当前选中节点展开
this.$refs.tree.store.nodesMap[
this.selectNodeData[this.nodeKey]
].expanded = true
})
}else if(!this.lazy) { // 两者都不是,并且不是懒加载,将值赋值给treeList
this.treeList = res.data
}
}
}
}
}
</script>
1.懒加载:
1-1.加载数据:设置lazy=true,即为懒加载方式,懒加载时,初始化会自动加载一次load函数,函数里进行数据请求:
loadTreeNode(node, resolve) {
this.requestTreeNode(node, resolve)
}
点击某个节点时,如果这个节点不是叶子节点,则会再次触发此函数获取下层数据,可根据所点击的节点数据,即参数node作为请求参数获取对应的数据。
1-2.更新节点:当对节点进行编辑、删除时,需要更新树,只需更新节点,不必更新全部的树即可。
原理:更新节点,其实更新的是该节点的子节点,不包括本节点。删除该节点的子节点,重新请求数据获取本节点的子节点数据进行重新渲染。
更新节点的通用方法:
// refreshNode:要刷新的节点;newNodeData:新的子节点数据
refreshNode.splice(0, refreshNode.length);
refreshNode.doCreateChildren(newNodeData);
1>.方法node-click调用函数menuNodeClick,记录点击的节点信息,对节点操作前,必然先点击选中某个节点。此函数监听点击节点事件,只要点击了节点,就触发:
menuNodeClick(data, node, treeNode) {
this.selectNodeData = data
this.selectNode = node
}
2>.节点操作后刷新节点即可,通过不同场景可以选择刷新本节点(node)还是刷新本节点的父节点(node.parent):
/**
* 刷新节点数据
* @node [node/Object]: 刷新的节点node,刷新node以下的所有节点
* @type [String]: node的类型,'node'表示node是树的节点信息Node;'data'表示node是树节点信息中的data数据
*/
refreshTreeNode(node, type) {
let refreshNode;
// 拿到要更新节点的子节点
if(type === 'node') {
refreshNode = node.childNodes
}else if(type === 'data') {
let getNode = this.$refs.tree.getNode(node)
refreshNode = getNode.childNodes
}
// 删除原有的子节点
refreshNode.splice(0, refreshNode.length);
//重新请求数据,更新节点
this.requestTreeNode(node)
}
1-3.选择框checkBox:
如果懒加载中,有选择框,需要将有选择框的数据加载出来,然后通过属性default-checked-keys来勾选,通过default-expanded-keys设置展开的节点。
1-4.单选:
如果在懒加载中,有单选项,则设置选中即可:
// 设置当前节点选中
this.$refs.tree.setCurrentKey(
this.selectNodeData[this.nodeKey]
)
不管是单选还是多选,在第一次加载时,后台要给的,不只是选中信息,还需要选中节点所在分支的所有节点信息,赋值给default-expanded-keys以便可使节点所在分支从上到选择项都展开。但往往,后台可能给的,只是选中值的信息,这就要前端自己封装数据,获取需要展开的分支信息。根据数据格式不同,用不同的方法,详情见1-5
1-5.数据封装和展开节点所在的树:
懒加载时,请求接口后,数据给出来可分为两种:
1).单层数据:
[
{...},
{...},
{...}
]
这种格式的数据,【点击一层,加载一层】、【点击一层,加载该点击层的多层子节点】两种情况都可以满足。第一种不需要进行数据处理;第二种情况,需要在每条数据中注入一个字段,用来关联父子节点,然后将数据封装处理成el-tree所需要的格式,用一个递归函数整合数据(假设关联字段为parentId,nodeKey为id,树的子节点字段为children,需要加载id为'5'的多层子节点)(注:递归函数会影响性能,谨慎使用):
/** 修改requestTreeNode中resolve情况下的代码 */
requestTreeNode(node, resolve) {
...
if(resolve) {
let [...requestList] = res.data // 拿到请求数据
let parentData = node.data // 拿到当前点击节点,即父节点数据
// 根据关联键筛选出第一层子节点
let firstChildrenList = requestList.filter(item => item.parentId === '5')
// 将筛选出来的数据塞给父节点
parentData.children = firstChildrenList
// 原数据删除筛选出来的数据,便于节省循环耗费
requestList.splice(requestList.findIndex(item => item.parentId === '5'), 1)
// 调用函数,递归出数据
this.getTreeData(firstChildrenList, requestList, parentData, resolve)
}
},
/**
* 递归整合数据
* @data[Array]: 需要循环查找下级节点的数据
* @list[Array]: 源数据数组,用来在里面找到符合条件的数据
* @treeData[Array]: 整合出来的数据
* @resolve: 函数传递的el-tree的参数,用来返回整合好的数据
*/
getTreeData(data, list, treeData, resolve) {
if(list.length > 0) { // 如果list中还有数据,说明还有数据没有整合
data.forEach(itemData => { // 查找每个分支的子节点
let itemId = itemData.id
let itemChildren = list.filter(item => item.parentId === itemId)
if(itemChildren.length > 0) {
itemData.children = itemChildren
// 在list将筛选的数据删除
itemChildren.forEach(child => {
list.splice(
list.findIndex(item => item.parentId === itemId),
1
)
})
// 调用本函数继续向下查找
this.getTreeData(itemChildren, list, treeData, resolve)
}
})
}else { // 否则,说明list中数据已经全部整合完,resolve出数据
resolve(treeData)
}
}
选中值整合并展开选中值所在的树:写一个递归函数,根据给的选中值,将相关分支展开(这个函数要在resolve(treeData)后渲染完成后的$nextTick中执行):
/**
* 根据选中的值,向上查找,将父节点全部展开
* @selectData[object]: 选中项的数据信息
*/
expandSelectNode(selectData) {
if(selectData.parentId) { // 如果有parentId,则说明还有父节点,继续向上查找
// 设置展开
this.$refs.tree.store.nodesMap[
selectData.parentId
].expanded = true;
// 查找parentData的父节点信息
let [parentData] = treeList.filter(item => item.id === selectData.parentId)
// 调用本函数,向上查找
this.expandSelectNode(parentData)
}
}
2).多层数据(假设子节点的属性名是children):
[
{
...,
children: [
{
...,
children: [
{
...,
children: [...],
},
{
...,
children: [...],
}
]
},
{
...,
children: [
{
...,
children: [...],
}
]
}
]
}
]
这种格式的数据,单层、多层都可以满足,不需要做处理。
选中值整合并展开选中值所在的树:多层数据可由后台给出选中节点所在的整个分支的值,赋给default-expanded-keys进行展开。也可以自己做筛选,写一个递归函数,将多层数据循环,找到选中值的节点的分支来设置展开(假设nodeKey为id,树的子节点字段为children,需要展开id为'5'的节点所在的整个分支):
// 假设将获取的多层数据放置到this.treeList中
let expandList = this.loadExpandTree(this.treeList)
/**
* 对树进行递归,找出选中值所在的分支
* @list: 树或者分支的列表
* @nodeKeyList: 当前分支的检索到目前为止所有的id
* @parentId: 当前检索list的父级节点id
*/
loadExpandTree(list, nodeKeyList, parentId) {
let selectList; // 返回值,用来return当前分支的所有数据id
let [...keyList] = nodeKeyList || [] // 一定要用深拷贝,不然数据会一直保留所检索的所有节点id
if(parentId) keyList.push(parentId)
for(let index = 0; index < list.length; index += 1) { //循环查找
const item = list[index]
if (item.id === '5') { // 找到选中的节点,break
keyList.push(item.id)
selectList = keyList
break;
}else if(item.children && item.children.length > 0) { // 否则继续向下级查找
let findKeyList = this.loadExpandTree(item.children, keyList, item.id)
if(findKeyList) {
selectList = findKeyList
break;
}
}
}
return selectList
}
上述代码中expandList即为最终获取的选中节点以及向上整个分支的id数组合集,将expandList直接赋值给default-expanded-keys即可。
2.非懒加载:
2-1.加载数据:设置lazy=false,即为非懒加载方式,非懒加载时,初始化需调用函数来加载出所有的数据:
this.requestTreeNode()
2-2.更新节点:的非懒加载更新节点时,可要求后台写个接口,根据前端传的参数,按照要求返回部分要更新的数据,前端进行更新(更新方法见1-2)。也可以重新请求整个树的数据,但是需要前端整理出需要刷新的数据进行更新(更新方法见1-2)。
不建议直接刷新树进行重新加载整个树,因为用户的行为上只是更新了某个节点,更新后需要保持更新前所展开的节点继续展开。如果重新刷新整个树,还需要记录更新前展开的所有分支,用来刷新后继续展开,这样操作比较麻烦,也没有多大意义。
2-3.选择框checkBox:
如果非懒加载中,有选择框,需要将有选择框的数据通过属性default-checked-keys来勾选,通过default-expanded-keys设置展开的节点。
2-4.单选:
如果在非懒加载中,有单选项,则设置选中即可:
// 设置当前节点选中
this.$refs.tree.setCurrentKey(
this.selectNodeData[this.nodeKey]
)
2-5.数据封装和展开节点所在的树:
与懒加载情况相同,详情见1-5。
完。