前端组件库造轮子——Tree组件开发教程
前端组件库造轮子——Tree组件开发教程
前言
本系列旨在记录前端组件库开发经验,我们的组件库项目目前已在Github开源,下面是项目的部分组件。文章会详细介绍一些造组件库轮子的技巧并且最后会给出完整的演示demo。

文章旨在总结经验,开源分享,有问题的话也希望路过的大佬指正。
组件开发流程
组件递归
Tree组件算是比较有难度的组件了,其核心功能其实就是实现树一样的联级结构。其实实现就是组件递归。
我们来复习一下递归代码
我们的递归代码实现,必然是由一个函数和调用函数组成的。同理,要实现组件递归也需要做类似的操作。
function dfs() {...
}function Main() {dfs()
}
在组件递归中,我们就需要类比递归函数的操作,我们需要用一个组件node来作为递归组件,这个组件起到主要渲染的作用,并且需要一个tree组件,来调用组件执行。
🆗,现在知道了大致思路,我们在补充一下如何编写组件。
对于递归函数,很重要的一点,我们如何让他不断递归同时让他停下来。
我们可以利用props把参数传进去,然后在渲染的时候去判断有没有孩子,如果没有孩子就不渲染,这个可以用v-if来完成。
// node 组件中
<div v-if="isRender" v-show="items.isOpen"><nodev-for="(child, index) in items.children":key="index":items="child":label="label":children="children"></node>
</div>// 判断是否要渲染
const isRender = computed(() => {return (props.items.children && props.items.children.length);
});
那这样我们就可以实现node组件的正确递归,所以我们只需在tree组件中在调用一次node组件就可以了。
<div class="tree"><nodev-for="(item, index) in copyData":key="index":items="item":label="label":children="children"></node></div>
深拷贝和初始化
还没完,我们需要对传进来的数据做一些深拷贝和初始化。
为什么要深拷贝应该知道吧?vue中props是单向数据流,我们是不能直接修改的,因此我们需要深拷贝一份来操作。
const deepCopy = (target: any, hash_table = new WeakMap()) => {if (typeof target === "object") {let clone = Array.isArray(target) ? [] : {};if (hash_table.get(target)) return hash_table.get(target);hash_table.set(target, clone);for (const key in target) {clone[key] = deepCopy(target[key], hash_table);}return clone;} else {return target;}
};
为什么要初始化呢?因为在开发tree还需要预设置很多数据,例如:是否展开?那需要实现展开的功能,那么每个节点必然需要一个isOpen来控制,除此之外,还有很多其它的功能,比如判断层级等。
interface dataType {label: string;children?: dataType[];isOpen: boolean;
}
const copyData = ref([]);
onMounted(() => {copyData.value = init(deepCopy(props.data));
});const init = (data: dataType[]) => {if (!data.length || !data) return [];let res = [];for (let i = 0; i < data.length; i++) {const child = data[i];const children = init(child[props.children] || []);const label = child[props.label];const isOpen = false;res.push({label,children,isOpen,});}return res;
};
展开和收缩
接下来,我们实现如何渲染节点和展开,这个其实很简单,我们只要在递归组件上面补充我们的想要插入的数据即可,同时绑定好事件,利用isOpen属性来实现展开收缩,我们只需要在渲染v-if上在添加v-show即可。
<ul class="tree-node"><div class="tree-node-content" @click.stop="handleToggle(items)"><span>{{ items.label }}</span></div><div v-if="isRender" v-show="items.isOpen"><nodev-for="(child, index) in items.children":key="index":items="child":label="label":children="children"></node></div>
</ul>const handleToggle = (item: any) => {item.isOpen = !item.isOpen;
};
参数设置
接下来我们来设置一些参数,因为我们不清楚用户传进来的树结构的属性是什么样子的,因此我们可以用参数来标识,比如用children来标识子节点,这些东西就可以自由发挥了。
const props = defineProps({data: {type: Array,default: () => [],},label: {type: String,default: "label",},children: {type: String,default: "children",},
});
🆗到此为止,我们就把核心功能实现完成,其实基础的功能并没有多困难,后续会补充源码。

懒加载优化
在这里我补充一个优化吧,一个简单的懒加载可以是这样的,只渲染第一层,深层的如果没有点击过就不去渲染。
这个实现思路也很容易,再增加一个isLazy参数,在初始化的时候给每个节点绑定上isLazy,在渲染时v-if增加判断isLazy就可以了。在点击的时候再把isLazy取消即可。
在参考了element的源码后,他们的懒加载还可以传入一个load函数,并用isLeft来标识动态加入新的数据。参考链接
其实实现起来也不难,我们只需要多传入一个load函数,在点击时调用该函数,并且new Promise来回调执行即可。
const init = (data: dataType[], level: number) => {if (!data.length || !data) return [];let res = [];for (let i = 0; i < data.length; i++) {const child = data[i];const children = init(child[props.children] || [], level + 1);const label = child[props.label];const isOpen = false;
+ const isLazy = props.isLazy;
+ const isLeft = child["isLeft"] || false;res.push({label,children,isOpen,isLazy,
+ isLeft,
+ level,});}return res;
};
点击后加载数据
const handleToggle = async (item: any) => {item.isOpen = !item.isOpen;if (item.isLazy) {if (item.isLeft && props.load) {await new Promise((resolve) => {props.load(item, resolve);}).then((res: any) => {for (let i = 0; i < res.length; i++) {res[i].isLazy = item.isLazy;res[i].level = item.level + 1;}item.children = res.slice();}).catch((err) => {console.log("[Tree Component] load Funtion Error", err);});}item.isLazy = false;}
};
演示demo
完整项目demo
结语
Tree组件的核心开发功能就是上面这些,其他更多的详细功能开发可以参考Hview-ui项目源码
如果想要了解更多的组件轮子开发,或者组件库开发流程,更多详细的组件开发过程更新在GitHub项目源码,最后觉得我们项目or文章不错可以点个star,点点小手支持一下,也欢迎各路大佬为我们的开源项目添砖加瓦。
相关文章:
前端组件库造轮子——Tree组件开发教程
前端组件库造轮子——Tree组件开发教程 前言 本系列旨在记录前端组件库开发经验,我们的组件库项目目前已在Github开源,下面是项目的部分组件。文章会详细介绍一些造组件库轮子的技巧并且最后会给出完整的演示demo。 文章旨在总结经验,开源分…...
java打war包、jar包方式,java运行war包、jar包方式
Java spring boot部署到生产环境有两种常见方式 1打jar包,使用了内置的tomcat服务器,流程简单 2打war包,可以放标准tomcat服务器中 jar包 1pom.xml新增 <build><plugins><plugin><groupId>org.springframework.b…...
“超级AI助手:全新提升!中文NLP训练框架,快速上手,海量训练数据,ChatGLM-v2、中文Bloom、Dolly_v2_3b助您实现更智能的应用!”
“超级AI助手:全新提升!中文NLP训练框架,快速上手,海量训练数据,ChatGLM-v2、中文Bloom、Dolly_v2_3b助您实现更智能的应用!” 1.简介 目标:基于pytorch、transformers做中文领域的nlp开箱即用…...
空时自适应处理用于机载雷达——机载阵列雷达信号环境(Matla代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
lib61850 学习笔记一 (概念)
IEC61850 定义60多种服务满足变电站通信需求。支持在线获取数据模型,也支持IED水平通信(GOOSE报文) 术语定义 间隔 bay: 变电站由据应公共功能紧密连接的子部分组成。 例如 介于进线或者 出线 和母线之间的断路器;二条母线之间…...
【深度学习】半监督学习 Efficient Teacher: Semi-Supervised Object Detection for YOLOv5
https://arxiv.org/abs/2302.07577 https://github.com/AlibabaResearch/efficientteacher 文章目录 AbstractIntroductionRelated WorkEfficient TeacherDense Detector Abstract 半监督目标检测(SSOD)在改善R-CNN系列和无锚点检测器的性能方面取得了成…...
vue3鼠标拖拽滑动效果
第一步 在utils下面新建一个directives.js文件,然后引入如下代码 const dragscroll (el) > {el.onmousedown ev > {const disX ev.clientX;const disY ev.clientY; // 需要上下移动可以加const originalScrollLeft el.scrollLeft;const originalScroll…...
08 通过从 库1 复制 *.ibd 到 库2 导致 mysql 启动报错
前言 呵呵 最近同事有这样的一个需求 需要将 库1 的一张表 复制到 库2 然后 我想到了 之前一直使用的通过复制这个库的 data 文件来进行数据迁移的思路, 是需要复制这个 库对应的 data 目录下的数据文件, 以及 ibdata1 文件 然后 我又在想 这里的场景能否也使用这里的额方式…...
一生一芯9——ubuntu22.04安装valgrind
这里安装的valgrind版本是3.19.0 下载安装包 在选定的目录下打开终端,输入以下指令 wget https://sourceware.org/pub/valgrind/valgrind-3.19.0.tar.bz2直至下载完成 解压安装包 输入下面指令解压安装包 tar -xvf valgrind-3.19.0.tar.bz2.tar.bz2注…...
STM32中BOOT的作用 (芯片死锁解决方法)
BOOT stm32中具有BOOT1和BOOT0 作用 BOOT是stm32单片机的启动模式, 通过不同组合模式,共有三种启动方式。 一般来说就是指我们下好程序后,重启芯片时,SYSCLK的第4个上升沿,BOOT引脚的值将被锁存。用户可以通过设置B…...
基于YOLOv8模型和DarkFace数据集的黑夜人脸检测系统(PyTorch+Pyside6+YOLOv8模型)
摘要:基于YOLOv8模型和DarkFace数据集的黑夜人脸检测系统可用于日常生活中检测与定位黑夜下的人脸,利用深度学习算法可实现图片、视频、摄像头等方式的目标检测,另外本系统还支持图片、视频等格式的结果可视化与结果导出。本系统采用YOLOv8目…...
C++中<iostream> 的cin >> str 和<string>的getline(cin, str) 用来读取用户输入的两种不同方式的不同点
C中<iostream> 的cin >> str 和<string>的getline(cin, str) 用来读取用户输入的两种不同方式的不同点 <string>的getline()函数语法如下【https://cplusplus.com/reference/string/string/getline/】: istream& getl…...
微信报修系统有什么优势?怎么提升企业维修工作效率与管理水平?
随着智能化时代的到来,企业、事业单位的现代化设备数量和种类不断增加,原本繁琐的报修、填写记录、检修管理等工作得以简化。从发起报修到维修,以及维修之后给予评价的整个过程,通过手机微信报修系统均能看到,既省时又…...
11.2.1-通货膨胀CPI
文章目录 1. 什么是CPI2. 在哪里获取CPI数据3. CPI的同比、环比到底是什么意思?4. 计算购买力侵蚀5. 复利计算 微不足道的小事也会引发惊人的结果, 每念及此, 我就认为世上无小事。——布鲁斯巴登(Bruce Barton) 核心内…...
服务器基础
0x01基础 介绍 可以理解为企业级的电脑,比个人使用的电脑具备更强的配置、性能、可靠性及稳定性。设计工艺和器件全部采用企业级设计,保障7*24小时稳定运行。 演进历史 处理性能 外观 发展方向 分类 按外形分类 按高度分类 按应用分类 按综合能力…...
mybatis中#{ }和${ }的区别
先说结论:二者肯定是有区别的 区别总结 ${ } 直接的 字符串 替换,在mybatis的动态 SQL 解析阶段将会进行变量替换。 #{ } 通过预编译,用占位符的方式?传值可以把一些特殊的字符进行转义,这样可以防止一些sql注入。 举例说明区…...
【真人语音】讯飞星火个人声音训练及导出下载工具V0.2.exe
【项目背景】 小编一直在尝试着短视频技术,在读文案的时候经常会读错;所以,只能用微软或者剪映的文本转语音软件。 很早之前在Github上也看到过真人人声训练的开源代码,尝试过一番之后,也是以失败告终;就…...
正中优配:创业板指大涨3.47%!减速器等概念板块掀涨停潮!
周二(8月29日),三大股指团体涨超1%。截至上午收盘,上证指数涨1.39%,报3141.82点;深证成指和创业板指别离涨2.41%和3.47%;沪深两市算计成交额6264.51亿元,总体来看,两市个股涨多跌少&…...
多功能租车平台微信小程序源码 汽车租赁平台源码 摩托车租车平台源码 汽车租赁小程序源码
多功能租车平台微信小程序源码是一款用于汽车租赁的平台程序源码。它提供了丰富的功能,可以用于租赁各种类型的车辆,包括汽车和摩托车。 这个小程序源码可以帮助用户方便地租赁车辆。用户可以通过小程序浏览车辆列表,查看车辆的详细信息&…...
spring事件和线程池区别
Spring事件(Spring Event)和线程池(Thread Pool)是两个不同的概念。 Spring事件是Spring框架中的一种机制,用于在应用程序中实现发布-订阅模式。通过定义事件和监听器,可以在特定事件发生时,通…...
理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...
STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...
AGain DB和倍数增益的关系
我在设置一款索尼CMOS芯片时,Again增益0db变化为6DB,画面的变化只有2倍DN的增益,比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析: 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...
IP如何挑?2025年海外专线IP如何购买?
你花了时间和预算买了IP,结果IP质量不佳,项目效率低下不说,还可能带来莫名的网络问题,是不是太闹心了?尤其是在面对海外专线IP时,到底怎么才能买到适合自己的呢?所以,挑IP绝对是个技…...
JS手写代码篇----使用Promise封装AJAX请求
15、使用Promise封装AJAX请求 promise就有reject和resolve了,就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...
第八部分:阶段项目 6:构建 React 前端应用
现在,是时候将你学到的 React 基础知识付诸实践,构建一个简单的前端应用来模拟与后端 API 的交互了。在这个阶段,你可以先使用模拟数据,或者如果你的后端 API(阶段项目 5)已经搭建好,可以直接连…...
深入浅出WebGL:在浏览器中解锁3D世界的魔法钥匙
WebGL:在浏览器中解锁3D世界的魔法钥匙 引言:网页的边界正在消失 在数字化浪潮的推动下,网页早已不再是静态信息的展示窗口。如今,我们可以在浏览器中体验逼真的3D游戏、交互式数据可视化、虚拟实验室,甚至沉浸式的V…...
