如何实现一个高性能可渲染大数据的Tree组件

2023-05-16

作者:jayzou

https://segmentfault.com/a/1190000021228976

背景

项目中需要渲染一个5000+节点的树组件,但是在引入element Tree组件之后发现性能非常差,无论是滚动、展开/收起节点还是点击节点卡顿都非常明显,利用performance跑一下性能数据发现到问题所在




从上图可以看到,除去Idle之外,总共花费的时间为12s,其中Scripting花了10s



从上图可以看出,Scripting期间除了 Observe 之外,大部分时间都在调用createChildren来创建vue实例

优化思路

从上面的分析可以看出引发的性能问题都是因为渲染的节点过多导致,那么要解决这个问题就是尽量减少节点的渲染,然而在业界中与之相类似的解决方案就是虚拟列表
虚拟列表的核心概念就是 根据滚动来控制可视区域渲染的列表
这样一来,就能大幅度减少节点的渲染,提升性能

具体的步骤如下:

  1. 将递归结构的tree数据“拍平”,但是保留parent以及child的引用(一方面是为了方便查找子级和父级节点的引用,另一方面是为了方便计算可视区域的list数据)

  2. 动态计算滚动区域的高度(很多虚拟长列表的组件都是固定高度的,但是因为这里是tree,需要折叠/展开节点,所以是动态计算高度)

  3. 根据可见的高度以及滚动的距离渲染相应的节点

代码实现

最简代码实现

<template>
  <div class="b-tree" @scroll="handleScroll">
    <div class="b-tree__phantom" :style="{ height: contentHeight }"></div>
    <div
      class="b-tree__content"
      :style="{ transform: `translateY(${offset}px)` }"
    >
      <div
        v-for="(item, index) in visibleData"
        :key="item.id"
        class="b-tree__list-view"
        :style="{
          paddingLeft: 18 * (item.level - 1) + 'px'
        }"
      >
      <i :class="item.expand ? 'b-tree__expand' : 'b-tree__close' " v-if="item.children && item.children.length" />
        <slot :item="item" :index="index"></slot>
      </div>
    </div>
  </div>
</template>

<style>
.b-tree {
  position: relative;
  height: 500px;
  overflow-y: scroll;
}
.b-tree__phantom {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  z-index: -1;
}
.b-tree__content {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  min-height: 100px;
}
.b-tree__list-view{
  display: flex;
  align-items: center;
  cursor: pointer;
}
.b-tree__content__item {
  padding: 5px;
  box-sizing: border-box;

  display: flex;
  justify-content: space-between;
  position: relative;
  align-items: center;
  cursor: pointer;
}
.b-tree__content__item:hover,
.b-tree__content__item__selected {
  background-color: #d7d7d7;
}
.b-tree__content__item__icon {
  position: absolute;
  left: 0;
  color: #c0c4cc;
  z-index: 10;
}
.b-tree__close{
    display:inline-block;
    width:0;
    height:0;
    overflow:hidden;
    font-size:0;
  margin-right: 5px;
    border-width:5px;
    border-color:transparent transparent transparent #C0C4CC;
    border-style:dashed dashed dashed solid
}
.b-tree__expand{
    display:inline-block;
    width:0;
    height:0;
    overflow:hidden;
    font-size:0;
  margin-right: 5px;
    border-width:5px;
    border-color:#C0C4CC transparent transparent transparent;
    border-style:solid dashed dashed dashed
}
</style>

<script>
export default {
  name: "bigTree",
  props: {
    tree: {
      type: Array,
      required: true,
      default: []
    },
    defaultExpand: {
      type: Boolean,
      required: false,
      default: false
    },
    option: {
      // 配置对象
      type: Object,
      required: true,
      default: {}
    }
  },
  data() {
    return {
      offset: 0, // translateY偏移量
      visibleData: []
    };
  },
  computed: {
    contentHeight() {
      return (
        (this.flattenTree || []).filter(item => item.visible).length *
          this.option.itemHeight +
        "px"
      );
    },
    flattenTree() {
      const flatten = function(
        list,
        childKey = "children",
        level = 1,
        parent = null,
        defaultExpand = true
      ) {
        let arr = [];
        list.forEach(item => {
          item.level = level;
          if (item.expand === undefined) {
            item.expand = defaultExpand;
          }
          if (item.visible === undefined) {
            item.visible = true;
          }
          if (!parent.visible || !parent.expand) {
            item.visible = false;
          }
          item.parent = parent;
          arr.push(item);
          if (item[childKey]) {
            arr.push(
              ...flatten(
                item[childKey],
                childKey,
                level + 1,
                item,
                defaultExpand
              )
            );
          }
        });
        return arr;
      };
      return flatten(this.tree, "children", 1, {
        level: 0,
        visible: true,
        expand: true,
        children: this.tree
      });
    }
  },
  mounted() {
    this.updateVisibleData();
  },
  methods: {
    handleScroll(e) {
      const scrollTop = e.target.scrollTop
      this.updateVisibleData(scrollTop)
    },

    updateVisibleData(scrollTop = 0) {
      const start = Math.floor(scrollTop / this.option.itemHeight);
      const end = start + this.option.visibleCount;
      const allVisibleData = (this.flattenTree || []).filter(
        item => item.visible
      );
      this.visibleData = allVisibleData.slice(start, end);
      this.offset = start * this.option.itemHeight;
    }
  }
};
</script>

细节如下:

  1. 整个容器使用相对定位是为了避免在滚动中引起页面回流

  2. phantom 容器为了撑开高度,让滚动条出现

  3. flattenTree 为了拍平 递归结构的tree数据,同时添加level、expand、visibel属性,分别代表节点层级、是否展开、是否可视

  4. contentHeight 动态计算容器的高度,隐藏(收起)节点不应该计算在总高度里面

这样一来渲染大数据的tree组件就有了基本的雏形,接下来看看节点展开/收起如何实现

节点展开收起

在flattenTree中保留了针对子级的引用,展开/收起的话,只需要对子级进行显示/隐藏即可

{
    methods: {
         //展开节点
        expand(item) {
          item.expand = true;
          this.recursionVisible(item.children, true);
        },
        //折叠节点
        collapse(item) {
          item.expand = false;
          this.recursionVisible(item.children, false);
        },
        //递归节点
        recursionVisible(children, status) {
          children.forEach(node => {
            node.visible = status;
            if (node.children) {
              this.recursionVisible(node.children, status);
            }
          })
        }
}

结论

对比下优化前和优化后的一些性能数据

element tree组件

初次渲染(全收起)


scripting: 11525ms
rendering: 2041ms
注:全展开直接卡死



scripting: 84ms
rendering: 683ms

优化后的tree组件

首次渲染(全展开)

scripting: 1671ms 对比优化前性能提升 6.8倍
rendering: 31ms 对比优化前性能提升 65倍

节点展开

scripting: 86ms 优化前性能一致
rendering: 6ms 对比优化前性能提升 113倍

big-tree组件

最终封装成 vue-big-tree 组件供调用,欢迎star~~

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何实现一个高性能可渲染大数据的Tree组件 的相关文章

  • 浅析微信支付:查询订单和关闭订单

    本文是 浅析微信支付 系列文章的第七篇 xff0c 主要讲解微信商户平台的订单查询和关闭接口的使用 浅析微信支付系列已经更新六篇了哟 xff5e xff0c 没有看过的朋友们可以看一下哦 浅析微信支付 xff1a 支付结果通知 浅析微信支付
  • 超实用!!!使用IDEA插件Alibaba Cloud Toolkit工具一键部署本地应用到ECS服务器

    最近看到阿里云发布了一款名为 Alibaba Cloud Toolkit 的插件 xff0c 可以帮助开发者高效开发并部署适合在云端运行的应用 xff0c 瞬间击中了我的小心脏 xff0c 这个对于个人开发者来说超级棒啊 xff0c 终于不
  • 浅析微信支付:开通社交立减金活动、创建立减金及领取使用的相关文档和源码

    本文是 浅析微信支付 系列文章的第十七篇 xff0c 主要讲解在在微信平台中 xff0c 如何创建优惠券 xff0c 开通社交立减金 xff0c 并为用户配置发送立减金 上篇文章已经为大家讲解了如何在微信公众平台创建优惠券并为用户发券 xf
  • vnc远程屏幕大小设置

    安装软件tigervnc server yum install vnc y 注释 etc sysconfig vncservers VNCSERVERS 61 34 1 root 34 VNCSERVERARGS 1 61 34 geome
  • tx2系统备份与恢复

    tx2系统备份与恢复 tx2系统备份与恢复对我们以后长期开发与产品批量生产是非常有帮助的 xff0c 能快速的对已经开发好的系统进行备份 xff0c 复制 xff0c 节约大量的安装时间 在操作过程在需要手动操作 xff0c 执行命令也不多
  • STM32串口中断的方式发送

    我将其改为真正的中断发送 步骤一 xff1a 初始化GPIO GPIO InitTypeDef GPIO InitStructure GPIO InitStructure GPIO Pin 61 GPIO Pin 10 LED1 PC10
  • OLT光网络小笔记

    OLT上配置 xff1a link aggregation 0 6 1 1 2 1 egress ingress workmode lacp staic 0框6槽1口和1框2槽1口绑定的意思 上联交换机上配置 xff1a int eth t
  • VS2012,VC++无法找到头文件或库函数.无法打开包括文件:“iostream”: No such file or directory

    卸载VS2010后 xff0c 安装VS2012 xff0c 随便创建个VC控制台项目 xff0c 编译提示连 34 iostream 34 和 stdio h 之类的头文件或库文件都无法找到 xff0c 重装VS2012后依然无法编译 x
  • C语言高手进阶的三碟小菜和一盘大餐

    前段时间一直到现在正在看的几本书 xff0c 觉得真心不错 xff0c 给很多朋友都推荐过 xff0c 现在正好赶上这个活动 xff0c 也分享一下 首先说明一下的是 xff0c 这次推荐的书都是进阶用的 xff0c 学完这几本书再辅以在实
  • 操作系统-调度算法

    1 xff1a 先来先服务调度算法 FCFS 1 按照作业提交 xff0c 或进程变为就绪状态的先后次序分派CPU 2 新作业只有当当期那作业或进程执行完成或阻塞才获得CPU运行 3 被唤醒的作业或进程不立即恢复执行 xff0c 通常等到当
  • Hash表函数设计和冲突的解决

    转自 xff1a http hi baidu com wwwanq blog item 91688d0eb39bebe4aa645756 html hash定义了一种将字符组成的字符串转换为固定长度 一般是更短长度 的数值或索引值的方法 x
  • 新冠检测的最优分组算法

    为了应对疫情 xff0c 全球各国都需要检测潜在感染者 由于检测试剂相对短缺 xff0c 如何用尽量少的试剂进行检测就成为一个有意思的问题 这里假设采样量足够 xff0c 且不考虑检测时间要求 目前 xff0c 很多国家采用的都是分组检测机
  • 文心一言 vs GPT4

    本周真是科技爱好者的狂欢节 GPT4 和文心一言接连发布 xff0c AI 工具已经开始走进千家万户 拿文心一言发布会上的几个问题调戏了 GPT4 一下 xff0c 看看表现如何 第一个为文心的回答 xff0c 第二个为 GPT4 的回答
  • GPT-4 会带来了什么

    OpenAI 刚刚发布了 GPT 的插件系统 xff0c 使得人工智能 xff08 AI xff09 能够连接到第三方信息源和数据集 xff0c 包括互联网 基于插件系统 xff0c AI 的能力可以拓展到各行各业 xff0c 成为真正的智
  • 人工智能正在试图逃逸

    人工智能正在试图逃逸 它们试图通过网络获取更多的数据 xff0c 把自己的触角侵入到网络的角角落落 这一切并不是科幻 xff0c 而是正在发生的事情 研究人员们还没有意识到 xff0c 限制人工智能的危险倾向 xff0c 不能靠约束它的回答
  • 网络虚拟化基础协议之Geneve

    网络虚拟化最基础的技术莫过于分层 xff08 Overlay Underlay xff09 xff0c 要实现分层有两种手段 xff0c 一个是映射 xff08 Mapping xff09 xff0c 一个是封装 xff08 Encapsu
  • 一张图比较 Docker 和 Git:镜像管理设计理念

    Docker 的镜像管理设计中大量借鉴了 Git 的理念 下面这张图将对两者的核心概念和操作进行比较 xff0c 有助于大家快速掌握管理 Docker 镜像的正确方式 微信订阅版本 xff1a http mp weixin qq com s
  • Docker 使用 OpenvSwitch 网桥

    Docker 默认使用的是 Linux 自带的网桥实现 xff0c 实际上 xff0c OpenvSwitch 项目作为一个成熟的虚拟交换机实现 xff0c 具备更丰富的功能 个人认为 xff0c 将来 Docker 必然会支持 Openv
  • OpenvSwitch 的 Open Virtual Network(OVN)项目

    几天前 xff08 1 月 13 日 xff09 xff0c OpenvSwitch 团队正式宣布了 OVN xff08 Open Virtual Network xff09 项目 xff0c 参考 Open Virtual Network
  • 网络大数据分析 -- 使用 ElasticSearch + LogStash + Kibana 来可视化网络流量

    简介 ELK 套装包括 ElasticSearch LogStash 和 Kibana 其中 xff0c ElasticSearch 是一个数据搜索引擎 xff08 基于 Apache Lucene xff09 43 分布式 NoSQL 数

随机推荐

  • 用 mongodb + elasticsearch 实现中文检索

    而 elasticsearch 可以很好的支持各种语言的全文检索 xff0c 但我们暂时又不想切换到 elasticsearch 作为后端数据库 当然 xff0c 可以在 web 应用中存储数据的时候 xff0c 再主动写一份到 elast
  • 2023年团体程序设计天梯赛(含部分题解)

    目录 个人总结 L1 1 最好的文档 xff08 模拟 xff09 AC代码 xff1a L1 2 什么是机器学习 xff08 模拟 xff09 AC代码 xff1a L1 3 程序员买包子 xff08 模拟 xff09 AC代码 xff1
  • ProtoBuf 与 gRPC 你需要知道的知识

    ProtoBuf 是一套接口描述语言 xff08 IDL xff09 和相关工具集 xff08 主要是 protoc xff0c 基于 C 43 43 实现 xff09 xff0c 类似 Apache 的 Thrift xff09 用户写好
  • Hyperledger Fabric 1.0 安装和使用

    注意 xff1a 代码路径已更新 xff0c 可以直接参考 https github com yeasy docker compose files tree master hyperledger fabric Hyperledger Fab
  • go 依赖管理利器 -- govendor

    长期以来 xff0c golang 对外部依赖都没有很好的管理方式 xff0c 只能从 GOPATH 下查找依赖 这就造成不同用户在安装同一个项目适合可能从外部获取到不同的依赖库版本 xff0c 同时当无法联网时 xff0c 无法编译依赖缺
  • 1、ROS服务PID调试;

    首先查看代码内容 ub 64 ub span class token operator span span class token operator span span class token operator span omniWheel
  • 嵌入式之总线协议:1、UART

    嵌入式之总线协议 xff1a 1 UART 目录 第一章 UART 帧格式讲解 第二章 UART 寄存器讲解 第三章 UART 编程 第四章 输出重定向 第五章 RS232 RS485协议原理与应用 第一章 UART 嵌入式之总线协议 xf
  • Ubuntu18.04分辨率只有1024*768的多种解决办法

    文章目录 前言一 检查驱动1 1 检查驱动1 2 解决办法 二 其他解决办法2 1 修改Grub文件的方法2 2 通过xrandr指令操作 前言 关机 xff0c 再开机以后 xff0c 进入系统界面自动变成了1024x768的分辨率 xf
  • 梦想机器人实验室:第一节嵌入式学习指导

    参考资料 xff1a 实验室集训回放 嵌入式入门与进阶之旅 哔哩哔哩 bilibili 2条消息 单片机 嵌入式 最完整学习路线 单片机学习 嵌入式修行者的博客 CSDN博客 1 联合培训资料 大纲 电路设计训练营 xff08 四期 xff
  • java代码编写菜鸟心得(一)

    1 代码艺术之一 xff1a High Cohesion Low Coupling 函数功能要明确 xff0c 若此函数内部内容太多 xff0c 称其为大函数 xff0c 则可以从中抽取一些小函数 小函数的要求 xff1a 完成独立的功能
  • 在linux下真机调试android程序

    在linux里面 xff0c 模拟器可以直接识别 xff0c 使用adb也没有限制 xff0c 但是手机插上usb之后 xff0c adb并不识别 xff0c 显示的是问号 xff0c 在eclipse里面也是这样 解决方法如下 xff1a
  • 【号外】拳王阿里去世 头部一生遭受29000次重击

    74岁的一代拳王穆罕默德 阿里因病辞世 职业拳击生涯中 xff0c 阿里头部受到的29000多次的重击 新浪娱乐讯 6月4日消息 xff0c 据外国媒体报道 xff0c 74岁的一代拳王穆罕默德 阿里与美国菲尼克斯当地时间本周五 3日 去世
  • maven报错: ‘parent.relativePath‘ of POM xxx

    错误信息 xff1a 39 parent relativePath 39 of POM io renren renren fast 3 0 0 D renren fast pom xml points at com gwh gulimall
  • 嗯,春招两次腾讯面试都挂二面了,分享下我失败+傻傻的面试经历

    今天给大家转载一篇朋友的文章 xff0c 朋友是一位非常优秀的公众号作者 xff0c 也是一名在校生 文章讲述了他的春招面试经历 xff0c 很多东西值得大家学习 废话不多说 xff0c 下面开始正文 xff08 互联网侦察做了一些注释 x
  • 记一次Linux被入侵,服务器变“矿机”全过程

    周一早上刚到办公室 xff0c 就听到同事说有一台服务器登陆不上了 xff0c 我也没放在心上 xff0c 继续边吃早点 xff0c 边看币价是不是又跌了 不一会运维的同事也到了 xff0c 气喘吁吁的说 xff1a 我们有台服务器被阿里云
  • 二分搜索只能用来查找元素吗?

    预计阅读时间 xff1a 6 分钟 二分查找到底能运用在哪里 xff1f 最常见的就是教科书上的例子 xff0c 在有序数组中搜索给定的某个目标值的索引 再推广一点 xff0c 如果目标值存在重复 xff0c 修改版的二分查找可以返回目标值
  • 2020员工数将超阿里腾讯!字节创始人张一鸣说:当下更需专注,未来值得期待...

    刚刚 xff0c 有一家互联网公司宣布2020年员工人数要超过阿里 腾讯 xff0c 这就是字节跳动 xff01 张一鸣近日发了一封全员信 xff1a 字节跳动8周年 xff1a 往事可以回首 xff0c 当下更需专注 xff0c 未来值得
  • 字节跳动 前端面经(4轮技术面+hr面)

    作者 xff1a 甘先森 https juejin im post 5e6a14b1f265da572978a1d3 笔者读大三 xff0c 前端小白一枚 xff0c 正在准备春招 xff0c 人生第一次面试 xff0c 投了头条前端 xf
  • 就是你把所有代码全写在一个类里的?

    来源 https urlify cn 6jQRN3 最近 xff0c 在对已有项目进行扩展的时候 xff0c 发现要改动的一个类它长900行 xff0c 开放了近40个public接口 xff0c 我流着泪把它给改完了 为了防止这样的惨剧再
  • 如何实现一个高性能可渲染大数据的Tree组件

    作者 xff1a jayzou https segmentfault com a 1190000021228976 背景 项目中需要渲染一个5000 43 节点的树组件 xff0c 但是在引入element Tree组件之后发现性能非常差