前言:天下寥寥,苍生涂涂,各路编程,唯我前端。一直热衷前端,却只是初窥门径,寻提升之路未果,故只能通过叙述的方式记录工作中遇到的问题,但求表达明了,增进自己对前端知识的理解。tree结构数据在工作经常碰到,今天写篇文章记录下操作tree形状数据的心得。
文章核心内容以三分
- 一者,扁平数据转tree形数据
- 二者,tree形数据转扁平数据
- 后者(目标):结合antd,reactJs实现丝滑的search Tree(搜索树)
扁平数据转tree数据
- 首先,随意书写一段有关系的数据:
const data = [
{ name: "马云", key: 1 },
{ name: "前端技术专家", key: 3, parent: 2 },
{ name: "首席科学家", key: 2, parent: 1 },
{ name: "前端架构师", key: 4, parent: 3 },
{ name: "前端工程师", key: 5, parent: 4 },
{ name: "前端菜鸟", key: 6, parent: 5 },
{ name: "前端小白", key: 7, parent: 6 },
{ name: "马岳父", key: 8 },
{ name: "QQ", key: 9, parent: 8 },
{ name: "微信", key: 10, parent: 8 },
{ name: "王者荣耀", key: 11, parent: 8 },
{ name: "绝地求生", key: 12, parent: 8 },
{ name: "QQ会员", key: 13, parent: 9 },
{ name: "QQ空间", key: 14, parent: 9 },
{ name: "QQ钱包", key: 15, parent: 9 },
{ name: "沙漠地图", key: 16, parent: 12 },
{ name: "公众号", key: 17, parent: 10 },
{ name: "群聊", key: 18, parent: 10 },
{ name: "小程序", key: 19, parent: 10 },
{ name: "花木兰", key: 20, parent: 11 },
{ name: "芈月", key: 21, parent: 11 },
{ name: "马可波罗", key: 22, parent: 11 },
];
- 我写的数据最顶层父级是马云和马化腾,根据阿里职级标准,我们希望得到的结果是这样的:
- 根据化腾岳父和他的吸金帝国关系,我们希望得到的结果是这样的:
- 那么问题来了,如何得到这样的层次结构数据?我们一个一个来看;数据中的key代表当前数据的唯一标识,而parent则代表其父级。那么很明显,我们要做的就是:遍历data数组,根据parent和key的对应关系,生成tree数据,这里我们用children来装子数据。 话不多说,直接上才艺。
//封装getTree方法,将数组转化为tree.
import { cloneDeep } from "lodash";//lodash库里引入cloneDeep深克隆方法
const getTree = (data) => {
const temp = cloneDeep(data);//深克隆一份外来数据data,以防下面的处理修改data本身
const parents = temp.filter((item) => !item.parent); //过滤出最高父集
const children = temp.filter((item) => item.parent);//过滤出孩子节点
//遍历孩子节点,根据孩子的parent从temp里面寻找对应的node节点,将孩子添加在node的children属性之中。
children.map((item) => {
const node = temp.find((el) => el.key === item.parent);
node && (node.children ? node.children.push(item) : node.children = [item]);
});
return parents;//返回拼装好的数据。
};
- 封装好geTree方法,调用:
const tree=getTree(data);
console.log(tree);
- 打印结果马云如下:
- 化腾岳父如下:
- 数组转tree的内容到这里就结束了,有疑惑的朋友可以评论区留言讨论。
tree数据转扁平数据
- 数组转tree会了,那接下来就是tree转数组了。
- 先随意定义一段数据:
const treeData = [
{
title: "太极",
key: 1,
children: [
{
title: "两仪",
key: 2,
parent: 1,
children: [
{
title: "老阳",
key: 3,
parent: 2,
children: [{ title: "乾卦", key: 7, parent: 3 }],
},
{
title: "少阳",
key: 4,
parent: 2,
children: [
{ title: "巽卦", key: 9, parent: 4 },
{ title: "兑卦", key: 10, parent: 4 },
{ title: "离卦", key: 11, parent: 4 },
],
},
{
title: "老阴",
key: 5,
parent: 2,
children: [{ title: "坤卦", key: 8, parent: 5 }],
},
{
title: "少阴",
key: 6,
parent: 2,
children: [
{ title: "艮卦", key: 12, parent: 6 },
{ title: "坎卦", key: 13, parent: 6 },
{ title: "震卦", key: 14, parent: 6 },
],
},
],
},
],
},
];
- 子曰:“加我数年,五十以学易,可以无大过矣。”可见孔子对易经的推崇。以上数据是易经中太极到八卦演化过程,甚为奇妙,可以说宇宙万象皆在这演化规律之中:太极-》两仪-》四象-》八卦。我们要做的就是将这一段tree形数据转化为扁平结构。话不多说,直接上才艺。
const getFlatData=(data)=>{
const flatData=[];
data.map(item=>{
//解构当前item,去掉item中的children,将纯净的node(不含children的单条数据)添加到flatData中。如果children存在就递归执行getFlatData方法,直至添加了所有。
const {children,...node}=item
flatData.push(node);
children&&flagData.push(...getFlatData(children));
})
return flatData;
};
- 方法写好了,执行代码:
console.log(getFlatData(treeData));
- 效果如下:
- tree数据转扁平结构到这就结束了,最后让我们来实现一个有趣的功能,搜索树。
目标:antd+ReactJs实现丝滑的搜索树
- 首先,实现搜索框和树,我们默认展开所有的树节点,效果如下:
- 当搜索时,我们希望只出现搜索到的包含关键字的项和他的父级,怎么理解?看图:
- 当我们搜索“花木”时:
- 当我们搜索“花”时:
- 也就是说,当我们搜索时,我们要把原有的树结构多余部分拆解掉,只留下我们需要的部分。 怎么来实现呢?分为两步:第一步,找到包含关键字的项和他的父级。第二部:用我们之前封装好的getTree方法生成实时的tree结构。我们接下来就开始做。
- 首先,改造之前封装的getFlatData方法,给tree的孩子增加一个parents属性,用来标识当前孩子所有的父级。如下:
const getFlatData = (data) => {
const flagData = [];
data.map((item) => {
const { children, ...node } = item;
flagData.push(node);
+-----------
children &&
children.map((el) => {
el.parents = (item.parents ? item.parents + "," : "") + item.key;
}); //设置当前元素的所有父级。
+-----------
children && flagData.push(...getFlatData(children));
});
return flagData;
};
- 其实就增加了虚线部分一句代码。效果如下:
-
被处理过的数据会增加parents属性,标识当前元素的所有父级key值。
-
接下来开始界面编程:
import React, { useRef, useState } from "react";
import { cloneDeep } from "lodash";
import { Tree, Input } from "antd";
import {
DownOutlined,
} from "@ant-design/icons";
const SearchTree = () => {
const [expands, setExpands] = useState(() => data.map((item) => item.key));//初始化tree展开的key值
const flatData = useRef({});//ref变量,用来保存初始的tree数据和经过增强版getFlatData处理过的扁平结构数据。
const [treeData, setTreeData] = useState(() => {//初始化tree数据。
const tree = getTree(data);
flatData.current.flat = getFlatData(tree);//保存增强版getFlatData处理过的扁平结构数据。
flatData.current.tree = tree;//保存初始的tree数据
return tree;
});
const handleSearch = (e) => {};
return (
<div
style={{
width: 399,
height: 399,
display: "flex",
flexDirection: "column",
}}
>
<Input onChange={handleSearch} />
<Tree
showIcon
defaultExpandAll
expandedKeys={expands}
switcherIcon={<DownOutlined />}
treeData={treeData}
/>
</div>
);
};
export default SearchTree;
-
如图,界面很简易,只有一个输入框和一个Tree。初始化了三个state,三个state的作用看图上的解释。我们的重点在于实现handleSearch方法。
-
handleSearch方法实现如下:
const handleSearch = (e) => {
try {
const v = e?.target?.value?.trim();
if (!v?.length) {//搜索内容为空,初始化tree,默认全部展开
setExpands(data.map((item) => item.key));
setTreeData([...flatData.current.tree]);
} else {//搜索内容不为空,获取搜索项和其父级,实时生成tree
const keys = flatData.current.flat.filter((item) =>
item?.title?.includes(v)
);//获取搜索到的项
if (!keys?.length) {//没有搜到,将tree置为空,比如搜索“木兰小宝贝”,就会什么都没有
setExpands([]);
setTreeData([]);
return;
}
//以下逻辑处理keys不为空的情况
const expandsTemp = [];//需要展开的节点的key
const list = [];//存搜索项和其父级的数组
list.push(...keys);//先将搜索到的项存入list;
keys?.map((item) => {
const data = item.parents?.split(",").filter(Boolean);//拆分parents属性,获取当前项所有父级
data &&
data.map((el) => {
if (expandsTemp.every((_el) => _el !== el)) {
expandsTemp.push(el);//此判断保证expandsTemp里key值不重复
}
});
});
expandsTemp.map((item) => {
if (list.every((el) => el.key?.toString() !== item)) {//list里元素key值也要唯一。
const node = flatData.current.flat.find(
(el) => el.key?.toString() === item
);
node && list.push(node);
}
});//遍历expandsTemp,往list里添加搜索项的父级元素。
const tempTree = getTree(list);//根据list组装tree
setExpands([...expandsTemp.map((item) => Number(item))]);//map的作用是将expandsTemp里的key全部转为number;
setTreeData([...tempTree]);//设置实时tree
}
} catch (e) {}
};
- search方法的说明如图,难点就在于通过tree孩子元素的parents属性找到所有父级,不重复的添加到一个数组中,最后组装成tree结构数据。
完结语
- 所有内容到这里就结束了,撒花!尼采曾说:每一个不曾起舞的日子,都是对生命的辜负!所以,让我们快乐起来,起舞吧!