Element-ui Tree 详解:懒加载与非懒加载、选择框、节点刷新(手动刷新)、树数据封装、展开选择节点所在树

概述:对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。

完。

  • 1
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Element-UI 中的 Tree 组件支持,也就是说只有当节点展开时才会去节点下的子节点数据。这在处理大量数据时能够提高性能和用户体验。 要使用,需要为 Tree 组件设置 load 方法,该方法会在节点展开时被调用。load 方法接收一个参数 node,表示当前被展开节点。在 load 方法中,我们可以通过异步请求获取该节点下的子节点数据,并使用 resolve 方法将数据返回给 Tree 组件。 示例代码如下: ```html <template> <el-tree :data="data" :load="loadData"></el-tree> </template> <script> export default { data() { return { data: [ { label: '一级节点', children: true // 表示该节点有子节点,但未 } ] } }, methods: { loadData(node, resolve) { // 异步请求获取子节点数据 setTimeout(() => { const children = [ { label: '二级节点1' }, { label: '二级节点2' } ] // 将子节点数据返回给 Tree 组件 resolve(children) }, 1000) } } } </script> ``` 在上述示例中,我们通过设置 children 属性为 true 来表示该节点有子节点,但未。当该节点展开时,会调用 loadData 方法,该方法会通过异步请求获取子节点数据,并使用 resolve 方法将数据返回给 Tree 组件。返回的数据会自动渲染为该节点的子节点。 需要注意的是,load 方法需要返回一个 Promise 或在回调函数中调用 resolve 方法。如果返回的是 Promise,则需要在 Promise 中 resolve 子节点数据,例如: ```javascript loadData(node) { return new Promise(resolve => { // 异步请求获取子节点数据 setTimeout(() => { const children = [ { label: '二级节点1' }, { label: '二级节点2' } ] // 将子节点数据返回给 Tree 组件 resolve(children) }, 1000) }) } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值