Tree组件实现支持50W数据方法剖析

目录
  • 出师未捷身先死
    • Tree 自身的复杂性
    • 虚拟滚动带来的复杂性
  • 查问题
  • 怎么做
    • 缓存数据
    • 减少响应式数据
    • 用更快的 JS 语法
    • 扣细节
    • 数据结构一致性的魅力

出师未捷身先死

有用户在 fes-design VIP群 吐槽 Tree 组件在处理一万条左右数据时很卡。但是 fes-design 已重视大数据场景,提供基础的虚拟列表组件,以及选择器、表格、树形、级联等组件基于虚拟列表处理了大数据场景,为啥 Tree 组件还卡呢?

Tree 自身的复杂性

Tree 数据结构特性决定 Tree 组件中父子节点存在关联,以选中功能为例:

Select:选中只影响自身状态。

Tree:当开启父子关联时,选中某个节点时,其所有子孙节点全部选中,同时需计算父辈节点是否为全选中。

虚拟滚动带来的复杂性

虚拟滚动是指根据滚动距离计算当前视野范围需要展示的内容。不管有多少数据,只渲染视野范围内的选项,大大减少了 Vue 实例的创建,性能无比优越。因为虚拟滚动只接受一维数组结构,所以Tree 组件在初始化时需要把树状结构数据按照展示顺序拍平为一维数组。那么展开关闭的功能就变得复杂了!

不考虑虚拟滚动方案时节点会这么设计:

<div class="node">
    <div>{{ node.label }}</div>
    <div v-show="node.expanded" v-for="child in node.children">
            <Node node="child"/>
    </div>
</div>

展开关闭只需要改变 node.expanded

考虑虚拟滚动方案时节点会这么设计:

<div class="node">
    <div>{{ node.label }}</div>
</div>

计算所有子孙节点状态,判断节点是否显示,如果显示则把当前节点丢到虚拟滚动的一维数组中。

查问题

先用chrome的性能测试工具看看问题在哪:

可以找到耗时的代码语句,下一步干掉他们。

怎么做

缓存数据

Tree 组件在初始化时会把树状结构数据按照展示顺序拍平为一维数组,在这个过程中,记录每个节点的父级节点为indexPath 和所有子孙节点childrenPath。在后续逻辑中经常会用到:

// 当选中某个节点时,只需要处理此节点相关上下节点状态
if (checkingNode) {
    const { indexPath } = checkingNode;
    indexPath.slice(0).reverse().forEach(computeIndeterminate);
    checkingNode.hasChildren &&
        checkingNode.childrenPath.forEach(
            (key: TreeNodeKey) => {
                const node = nodeList.get(key);
                node.isIndeterminate.value = false;
            },
        );
    checkingNode = null;
}

减少响应式数据

在优化前所有节点都会丢到nodeList中:

const nodeList = reactive<TreeNodeList>({});

// 转换节点数据
const copy = transformNode(node, indexPath, level);
nodeList[copy.value] = copy;

数据量上来后,数据响应式处理耗时非常大。所以我们不要把整个对象一股脑弄成响应式的,只把需要的字段设置为响应式的。

Tree节点需要缓存的内部状态有是否开展、是否全选、是否选中,所以只需要这三个字段为响应式:

const nodeList: Map<TreeNodeKey, InnerTreeOption> = new Map();

f (!nodeList.get(value)) {
    // Object.assign比解构快很多
    copy = Object.assign({}, newItem);
    copy.isExpanded = ref(false);
    copy.isIndeterminate = ref(false);
    copy.isChecked = ref(false);
}

nodeList.set(copy.value, copy);

用更快的 JS 语法

1、Array.concat 性能比较慢,改为使用赋值

export function concat(arr: any[], arr2: any[]) {
    const arrLength = arr.length;
    const arr2Length = arr2.length;
    arr.length = arrLength + arr2Length;
    for (let i = 0; i < arr2Length; i++) {
    arr[arrLength + i] = arr2[i];
    }
    return arr;
}

2、Map 的查找性能比 Object 稍好

const nodeList = {} ;

改为使用

const nodeList = new Map();

3、解构语法比较慢,改为使用Object.assign

扣细节

1、computeCurrentData 是执行非常耗时的函数,由于 watch 两个变量,在初始化时会执行两次,加上debounce只需要执行一次。

watch(
    [currentExpandedKeys, transformData],
    debounce(() => {
        if (isSearchingRef.value) return;
        computeCurrentData();
    }, 10),
    {
        immediate: true,
    },
);

2、叶子节点不需要计算isExpanded

 if (node.hasChildren) {
    node.isExpanded.value = expandedKeys.includes(key);
 }

3、计算显示的节点时,可以先判断是否由展开或者关闭节点触发的计算,如果是则只需要计算此节点子孙和父级节点状态,而不需要计算全部节点

const computeCurrentData = ()=> {
    if(expandingNode) {
        // 计算此节点相关节点
        return
    }
    // 遍历所有节点
}

类似这种细节非常多,通过性能测试工具和自己经验能找到很多地方,积少成多,性能能提升不少。

数据结构一致性的魅力

以收起节点为例:

常规思路是:当点击收起节点时,判断当前所有子孙节点是否在显示数据数组中,如果在就删掉。复杂度是O(n^2)。

但是可以换个思路:由于childrenPath和currentData的顺序一致,只需要遍历一次childrenPath,判断是是否为当前节点下一个节点,如果是,删掉就好。复杂度是O(n)

const deleteNode = (keys: TreeNodeKey[], index: number) => {
    let len = 0;
    keys.forEach((key) => {
        if (key === currentData.value[index + len]) {
            len += 1;
        }
    });
    currentData.value.splice(index, len);
};

const index = currentData.value.indexOf(expandingNode.value);
deleteNode(expandingNode.childrenPath, index + 1);

Tree 的代码中有很多地方,可以通过特殊的数据结构来减少或者避免循环,性能提升非常大!

欢迎来体验:fes-design

以上就是Tree组件实现支持50W数据方法剖析的详细内容,更多关于Tree组件50W数据的资料请关注我们其它相关文章!

(0)

相关推荐

  • 关于antd tree 和父子组件之间的传值问题(react 总结)

    项目需求:点击产品树节点时获取该节点的所有父节点,同时回填表格的搜索条件,完成搜索功能,搜索结果展示在下方的table中. 写了三个组件: 现在有个业务场景交互:在orderTree组件中点击树节点,获取当前节点以及所有的父节点的Id 放入一个对象arrKeys中,并在orderForm组件中使用(回填类型下拉选择框,objId对象作为查询接口的入参) 现在可以分部解决问题: 1.首先获取点击的树节点以及所有父节点的id ---arrKeys 2.在点击树节点获取当前节点以及所有父级节点之后,通

  • Vue之vue-tree-color组件实现组织架构图案例详解

    目录 npm 安装loader Import Plugins 开始 排列方式 折叠展示 点击节点 其他功能 npm # use npm npm install vue-tree-color 安装loader npm install --save-dev less less-loader Import Plugins import Vue from 'vue' import Vue2OrgTree from 'vue-tree-color' Vue.use(Vue2OrgTree) 开始 因为已经

  • vue递归实现自定义tree组件

    本文实例为大家分享了vue递归实现自定义tree组件的具体代码,供大家参考,具体内容如下 1. 在tree/index.vue中: <template> <div> <ul> <item :model='data'></item> </ul> </div> </template> <script> import Item from './item' export default { componen

  • vue tree封装一个可选的树组件方式

    目录 组件实现的基本功能 先看效果图 组件实现的基本功能 1,根据后端返回的数据格式,传入组件动态的渲染出当前角色有哪些权限(新建,修改) 2,适配有2级和只有一级多选的数据 3,有全选(√) ,全不选 ,部分已选(-)的3装状态,每一级都支持(用的iview2次封装) 4,改变之后返回当前选中的所有权限的id,用于提交 5,手风琴效果,小屏适配 先看效果图 有部分权限没打开 打开 小屏 权限数据结构,select_status=1表示选中 默认添加没有权限的初始数据结构 有些数据只有一级子菜单

  • 如何实现vue的tree组件

    前言 Tree一直是大家熟知的组件,做一些大型的后台管理系统都会用到.使用树组件可以完整的展现其中的层级关系,并具有展开收起选择等交互功能. 效果 节点可以无限的递归延伸 可以展开和收起子节点 如果子节点全部选择对应的父节点也应该选中,反之父节点取消选中对应子节点也需要取消选中 API prop传递data属性,来描述所有的节点的信息 每个节点的配置描述如下 title: 展示的标题 expand 是否展开节点 checked 是否选中节点 children 子节点 以及还有两个event on

  • vue-tree-chart树形组件的实现(含鼠标右击事件)

    基于 vue-tree-chart,生成项目效果预览,包含鼠标右击事件: vue-tree-chart:https://github.com/tower1229/Vue-Tree-Chart 大家可以直接安装使用(具体事例可以查看官网) 但是个人建议最好是下载整个项目,封装成组件调用 基于官网初始代码,封装组件: <template> <table v-if="treeData.name"> <tr> <td :colspan="Ar

  • Tree组件实现支持50W数据方法剖析

    目录 出师未捷身先死 Tree 自身的复杂性 虚拟滚动带来的复杂性 查问题 怎么做 缓存数据 减少响应式数据 用更快的 JS 语法 扣细节 数据结构一致性的魅力 出师未捷身先死 有用户在 fes-design VIP群 吐槽 Tree 组件在处理一万条左右数据时很卡.但是 fes-design 已重视大数据场景,提供基础的虚拟列表组件,以及选择器.表格.树形.级联等组件基于虚拟列表处理了大数据场景,为啥 Tree 组件还卡呢? Tree 自身的复杂性 Tree 数据结构特性决定 Tree 组件中

  • EasyUI Tree树组件无限循环的解决方法

    在学习jquery easyui的tree组件的时候,在url为链接地址的时,发现如果最后一个节点的state为closed时,未节点显示为文件夹,单击会重新加载动态(Url:链接地址)形成无限循环.如: tree.json [{ "id":1, "text":"Folder1", "iconCls":"icon-save", "children":[{ "text"

  • React实践之Tree组件的使用方法

    本文介绍了React实践之Tree组件,分享给大家,具体如下: 实现功能 渲染数据 展开合并 使用 数据结构: const node = { title: '00000', key: '0' , level:'level1', open: true, child:[ { title: '0-111111', key: '0-0', level:'level2', open: true, child:[ { title: '0-1-1111', key: '0-0-0', level:'level

  • Element-ui tree组件自定义节点使用方法代码详解

    工作上使用到element-ui tree 组件,主要功能是要实现节点拖拽和置顶,通过自定义内容方法(render-content)渲染树代码如下~ <template> <div class="sortDiv"> <el-tree :data="sortData" draggable node-key="id" ref="sortTree" default-expand-all :expand-

  • Vue.js组件使用props传递数据的方法

    本文实例为大家分享了Vue.js使用props传递数据的具体代码,供大家参考,具体内容如下 基本用法 通常父组件的模板中包含子组件,父组件要正向地向子组件传递数据或参数,子组件接收到后根据参数的不同来渲染不同的内容或执行操作.这个正向传递数据的过程就是通过props来实现的. 在组件中,使用选项props来声明需要从父级接收的数据,props的值可以是两种,一种是字符串数组,一种是对象. 示例:构造一个数组,接收一个来自父组件的message,并把它再组件模板中渲染 <!DOCTYPE html

  • vue使用keep-alive实现组件切换时保存原组件数据方法

    前言 最近在做一个精品课程后台管理系统,其中涉及文件上传和文件列表展示,我不想将他们写入一个组件,故分开两个组件实现. 问题:但由于上传文件需要时间,这时要是用户切换别的组件查看时,上传文件组件就销毁了,导致文件上传失败. 追求效果:想利用keep-alive实现上传组件切换时仍继续上传文件,而其他组件则不会存活. 使用keep-alive的过程 普通方法:直接使用keep-alive <keep-alive> <router-view /> </keep-alive>

  • jquery使用EasyUI Tree异步加载JSON数据(生成树)

    这几天因为工作需要,要做一个支持无限级的菜单. 我也是菜鸟一只,能想到的东西不多,所以用了Easy UI的tree组件. 不得不说,easyui确实很强大. 因为是无限级菜单,数据量可能有点大,所以考虑采用异步加载. 但是因为后台默认传来的数据是 一个实体,所以又在后台进行了JSON字符串拼接. 最后,在网上找了N多代码,然后又去问了好几个群里的网友,终于搞出来这个小东西. 一.HTML部分代码 <div id="categoryChooseDiv" title="请选

  • layui.tree组件的使用以及搜索节点功能的实现

    由于项目树形节点比较多需要增加节点搜索功能,所以研究了一下加上社区伙伴的支持,目前功能可以简单实现但细节还需要修改,添加上了组件的基本使用方法和属性,现在分享出来~ HTML: <div class="layui-btn-container"> <button class="layui-btn layui-btn-sm" type="button" lay-demo="getChecked">获取选中节

  • 基于element-ui封装可搜索的懒加载tree组件的实现

    引言 最近开发项目时遇到一个需求就是采用tree的方式展示以万为单位的数据,因为数据量大第一反应就是采用"懒加载"的方式实现,为了方便用户在庞大的数据量中快速定位到某个节点搜索功能也是必不可少的:因为采用"懒加载"数据,搜索当然也是远程搜索了:确定了需求当然第一时间就去网上找有没有小伙伴已经实现了相关组件,最后....,还是自己实现一个吧(由于公司采用的element-ui所以基于el-tree进行改造):[这只是自己实现的一种思路,希望大家多多指正] 1.懒加载树

  • antd为Tree组件标题附加操作按钮功能

    目录 一.前言 二.实现方案 三.总结 一.前言 使用antd的tree组件实现下面这样的模块树,点击标题请求其下列表的数据,点击标题旁边的操作图标则执行对应的增删改功能: 二.实现方案 1.封装一个设置树标题的方法,通过开关改变state来控制图标按钮是否可见:  处理树数据(name.children) const setTree = (module_data: any) => { return module_data.map((item: any) => { let _json = {

随机推荐