一次100W+数据级别的渲染优化

news/2025/3/19 15:40:37/文章来源:https://www.cnblogs.com/renzhiwei2017/p/18781153

组织架构的列表页有关于公司人员架构的树形结构展示,某大客户有10万员工,造成组织架构的列表渲染卡顿,用户点击经常造成页面崩溃。

需求背景:左边是树形目录,多层级展示,层级结构未作限制。点击左边目录会展示对应的列表,点击右边对应用户的组织属性,也会联动左边的目录展示。

技术背景:vue2 + element el-tree

问题:1w数据页面展示无影响,当数据量达到5w+时页面开始卡顿甚至崩溃(导致公司某大客户持续投诉了一个月,他们有十几万员工)。

从技术角度来看,主要由以下原因造成:

1.虚拟dom的渲染开销

Vue 使用虚拟 DOM 来高效地更新真实 DOM。然而,虚拟 DOM 的创建和更新仍然需要一定的计算开销:

  • ​节点数量过多:1 万条数据意味着需要创建 1 万个虚拟 DOM 节点,这会占用大量内存和计算资源。
  • ​渲染时间过长:大量节点的渲染会导致主线程阻塞,页面出现卡顿。

2.响应式数据监听的开销

Vue 会对组件的 data 进行响应式处理,即递归地将数据属性转换为 getter/setter

3.组件生命周期钩子的执行

每个 el-tree 节点都是一个组件,Vue 会为每个组件执行生命周期钩子(如 created、mounted)

4.el-tree 的实现机制

​el-tree会一次性渲染所有节点,并且为每个节点绑定事件(如点击、展开/折叠等)

综合以上原因,结合需求背景,分析得出以下几点:

  1. 页面左右两块区域是互相联动,所有必须数据节点必须全部渲染出来
  2. 目录层级未作限制。根据客户实际情况,battle产品,最终将最大层级限制到5层
  3. 接口直接返回嵌套的数据结构,对于深度较大的树,递归调用栈会占用大量内存。改成使用扁平数据结构,修改z-tree适配vue进行目录渲染

show me the code

step 1:安装依赖, 当前ztree版本3.5.24

npm install ztree jquery

ztree初始化逻辑:

// 1. 引入样式和核心文件
import 'ztree/css/zTreeStyle/zTreeStyle.css'
import 'ztree'// 2. 定义容器
<div id="treeId" class="ztree"></div>// 3. 初始化实例(需在 DOM 加载完成后执行)
const setting = {}; // 配置项
const zNodes = [];  // 数据
const zTreeObj = $.fn.zTree.init($('#treeId'), setting, zNodes);

step 2: 封装ztree组件,创建ZTree.vue文件

<template><div :id="treeId" class="ztree"></div>
</template><script>
import 'ztree/css/zTreeStyle/zTreeStyle.css'
import 'ztree'export default {name: 'ZTree',props: {data: Array,      // 原始数据setting: Object   // zTree 配置},data() {return {treeId: `ztree_${Math.random().toString(36).substr(2, 9)}`, // 唯一 IDzTreeObj: null  // zTree 实例}},mounted() {this.initZTree(); // 初始化},methods: {initZTree() {this.zTreeObj = $.fn.zTree.init($(`#${this.treeId}`),this.setting,this.data);}}
}
</script>

通过 watch 监听数据变化,重新渲染:

watch: {data(newData) {if (this.zTreeObj) {this.zTreeObj.destroy(); // 销毁旧实例}this.initZTree(newData); // 重新初始化}
}

step3: 字段映射

接口返回数据字段如下

[{ id: 1, label: '根节点', parentId: null },{ id: 2, label: '子节点', parentId: 1 }
]

zTree 默认需要以下字段

[{ id: 1, name: '根节点', pId: null },{ id: 2, name: '子节点', pId: 1 }
]

在组件中新增 fieldMapping 属性,支持字段映射:

props: {fieldMapping: {type: Object,default: () => ({id: 'id',name: 'name',pId: 'pId'})}
}

通过计算属性 convertedData 转换数据:

computed: {convertedData() {return this.data.map(node => ({id: node[this.fieldMapping.id],name: node[this.fieldMapping.name],pId: node[this.fieldMapping.pId]}));}
}

ZTree.vue完整代码如下:

<template><div :id="treeId" class="ztree"></div>
</template><script>
import "ztree/css/zTreeStyle/zTreeStyle.css";
import "ztree";export default {name: "ZTree",props: {// 树数据data: {type: Array,default: () => [],},// 字段映射配置fieldMapping: {type: Object,default: () => ({id: "id", // 节点唯一标识字段name: "name", // 节点显示文本字段pId: "pId", // 父节点标识字段level: "level", // 层级字段(可选)}),},// zTree 配置setting: {type: Object,default: () => ({}),},},data() {return {treeId: `ztree_${Math.random().toString(36).substr(2, 9)}`, // 生成唯一 IDzTreeObj: null, // zTree 实例};},computed: {// 转换后的数据convertedData() {const mapping = this.fieldMapping;const data = this.data;// 一次遍历完成数据转换const result = data.map((node) => {const converted = {};Object.keys(mapping).forEach((key) => {const mapValue = mapping[key];converted[key] =typeof mapValue === "function"? mapValue(node) // 动态计算字段值: node[mapValue] ?? node[key]; // 直接映射字段值});return converted;});return result;},},watch: {// 监听数据变化,重新加载树convertedData: {handler(newData) {this.reloadTree(newData);},deep: true,},},mounted() {this.initZTree(); // 初始化 zTreeconsole.log("zTree 实例:", this.zTreeObj); // 打印 zTree 实例},methods: {// 初始化 zTreeinitZTree() {this.zTreeObj = window.$.fn.zTree.init(window.$(`#${this.treeId}`),this.setting,this.convertedData);},// 重新加载树reloadTree(data) {if (this.zTreeObj) {this.zTreeObj.destroy(); // 销毁旧实例}this.initZTree(data); // 初始化新实例},},
};
</script><style scoped>
.ztree {width: 100%;height: 100%;overflow: auto;
}
</style>

step4: 父组件调用ztree组件

启用扁平结构

setting: {data: {simpleData: {enable: true,    // 启用扁平模式idKey: 'id',     // 节点 ID 字段名pIdKey: 'pId'    // 父节点 ID 字段名}}
}

父组件代码如下:

<template><div id="app"><ZTree :data="treeData" :fieldMapping="fieldMap" :setting="treeSetting" /></div>
</template><script>
import ZTree from "./components/ZTree.vue";
import { generateFlatTreeData } from "./utils";export default {components: {ZTree,},data() {return {// 接口返回的扁平结构数据treeData: [{ id: 1, label: "根节点", level: 1, parentId: null },{ id: 2, label: "子节点 1", level: 2, parentId: 1 },{ id: 3, label: "子节点 2", level: 2, parentId: 1 },],// 字段映射规则fieldMap: {id: "id",name: "label", // 将接口的 label 字段映射为 zTree 的 name 字段pId: "parentId", // 将接口的 parentId 字段映射为 zTree 的 pId 字段level: "level",},// zTree 配置treeSetting: {data: {simpleData: {enable: true, // 启用扁平结构},},view: {showIcon: true, // 显示图标},},};},mounted() {// 模拟异步请求const data = generateFlatTreeData(5, 100000);// const data = generateFlatTreeData(5, 800000);console.log("data.length", data);this.treeData = data;},
};
</script><style>
#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;
}
</style>

项目完整代码见github: https://github.com/webLion200/vue-ztree

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/901559.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小程序 反编译

背景 小程序测试难点,数据包加密?有签名存在?导致测试受阻 工具 wedecode wedecode https://github.com/biggerstar/wedecode1.首次使用,源码安装方式 git clone https://github.com/biggerstar/wedecode npm install # 如果 npm 安装很慢, 可以使用右侧命令换国内的淘宝…

日志文件必须输出到控制台才香对吗

在实际工作中发现很多人喜欢将日志输出到控制台,有的甚至直接只是输出到控制台,都不输出到日志文件中。 这种操作看似人畜无害,实际上直接影响着系统的性能,很多时候还难以排查,这里我从实际举例都背后原因来分析为什么这么做并不香。通常的日志配置 这里我们使用经常使用…

C#通过FTP获取服务端文件

一、简介实际需求是在前端修改了配置文件后,由上位机统一分发给所有设备,因为下位机支持FTP协议,因此选用FTP来实现文件传输功能。 二、准备工作 1、FTP服务搭建FTP服务端选用FileZilla Server,免费开源,简单好用,可以下载中文版的。下载地址:下载 - FileZilla中文网,也…

20242942 2024-2025-2 《网络攻防实践》实验三

1.实验内容 (1)动手实践tcpdump 使用tcpdump开源软件对在本机上访问www.tianya.cn网站过程进行嗅探,回答问题:你在访问www.tianya.cn网站首页时,浏览器将访问多少个Web服务器?他们的IP地址都是什么? (2)动手实践Wireshark 使用Wireshark开源软件对在本机上以TELNET方式…

Oracle OCP认证没落了吗?

Oracle OCP认证没落了吗? Oracle的OCP认证是数据库领域必考的一个认证,但随着国产化的发展,国内很多企业开发了自己的数据库产品,这种情况对很多人造成了错误的认识:OCP被淘汰了吗?不然,从行业需求、技术趋势、认证体系变化等角度综合分析,Oracle OCP证书并未完全“没…

查看dll文件的publicKeyToken

输入: SN -T "C:\Program Files (x86)\Kingdee\K3Cloud\WebSite\bin\log4net.dll"

把 DeepSeek 接入电话系统后,不知疲倦的智能客服向我们走来了

我们基于deepseek和Freeswitch做了一个智能电话客服。 它会基于给定的FAQ知识库来回答问题,自动进行语音识别和语音合成。 语音识别我们采用的是开源的FunAsr,语音合成采用的是第三方商用的API接口。我们接下来介绍下它的内部组成部分。【 第一阶段】当电话拨通电话后,电话服…

Seata的工作模式

Seata的分布式模型中各个角色的作用: 1.TM(事务管理器)是分布式事务的发起方,负责定义全局事务的边界(开始,提交,回滚),并于TC交互协调事务状态。 核心职责: ​ 通过@GlobalTransctional注解标记全局事务的起点。 ​ 向TC注册全局事务 ​ 根据业务逻辑决定全局事务的提交或回滚…

dify升级

一、需求 从0.14.2升级到0.15.3,要求模型供应商,创建的应用数据等等,不能丢失。二、安装0.14.2 下载dify代码cd /optgit clone https://github.com/langgenius/dify.gitcd dify/切换到tag 0.14.2git checkout 0.14.2git pull origin 0.14.2运行difycd dockercp .env.example…

机器人弧焊电源气体省气装置的工作原理是什么?

焊接机器人节气装置,作为一种在焊接流程中至关重要的气体调控设备,其核心宗旨在于在确保焊接作业品质卓越的同时,通过高度精确的气体流量调控机制,有效缩减气体的消耗量,进而达成显著的节气效益。该装置详尽地集成了气体质量流量控制器、适配的电源适配器、精准的电流传感…

分享一次利用无问AI进行应急响应

前情摘要:最近勒索病毒真的太猖獗了,光一星期我就接连处理了两起。不过黑客的攻击方式都是大同小异,处理完报告之后,特来分享其中一起。各位可得小心小心在小心,千万不要中招,不然就只有重装的份了。 事件概述: 最近,某公司机房数据库中了勒索病毒,工作人员上班发现后…

广告子包边切割焊接工艺-代加工-外协加工-委外加工-激光代加工-河南郑州-芯晨微纳(河南)

一、普通焊接 普通焊接主要针对大型标识,如银行或家具城的大型广告字。这种焊接方式使用焊机进行,具体流程包括: (1)切割:根据所需的字体大小,使用不同的激光切割机进行切割处理。这一步不仅包括字面的切割,还包括围边的切割。 (2)焊接包边:在焊接过程中,需要非常精…