javascript tree
背景:前端日常业务开发的工作中,关于树状数据结构的增删改查肯定避免不了, 这里整理下日常通用的工具函数,方便下次快速改造。
类型、使用数据示例
//以下函数示例的类型合数据 参考这里定义。
type IList = {
id: number;
pid: number | null;
value: string;
};
type ITree = {
children?: ITree[];
} & IList;
let list: IList[] = [
{ id: 1, pid: null, value: "Item 1" },
{ id: 2, pid: 1, value: "Item 1-1" },
{ id: 3, pid: 1, value: "Item 1-2" },
{ id: 4, pid: 2, value: "Item 1-1-1" },
{ id: 5, pid: null, value: "Item 2" },
];
let tree: ITree[] = [
{
id: 1,
pid: null,
value: "Item 1",
children: [
{
id: 2,
pid: 1,
value: "Item 1-1",
children: [{ id: 4, pid: 2, value: "Item 1-1-1" }],
},
{ id: 3, pid: 1, value: "Item 1-2" },
],
},
{ id: 5, pid: null, value: "Item 2" },
];
线性列表转树结构
/**
* 匹配等于父节点的层,增加children,递归匹配查询
* @param oldData 线性列表数据源
* @param id 唯一标识
* @param newData 转完返回的树结构
* @returns
*/
const listToTree = (
oldData: IList[],
id: number | null,
newData: ITree[] = []
) => {
oldData.forEach((item) => {
if (item.pid === id) {
newData.push(item);
}
});
newData.forEach((i) => {
i.children = [];
listToTree(oldData, i.id, i.children);
if (i.children.length === 0) {
delete i.children;
}
});
return newData;
};
console.log(listToTree(list, null));
树结构转线性列表
/**
* 递归,把每一个节点push到新数组
* @param treeData tree数据源
* @param result 线性列表
* @returns
*/
const flattenTree = (treeData: ITree[], result: IList[] = []) => {
treeData.forEach((node) => {
result.push(node);
if (node.children && node.children?.length > 0) {
flattenTree(node.children, result);
delete node.children;
}
});
return result;
};
console.log(flattenTree(tree));
树:删除一个节点
/**
* 删除树结构对应条件的某一节点 (引用删除)
* @param treeData 数据源
* @param id 唯一标识
* @returns
*/
const removeTreeItem = (treeData: ITree[], id: number) => {
if (!treeData || !treeData.length) {
return;
}
for (let i = 0; i < treeData.length; i++) {
let node = treeData[i];
if (node.id === id) {
treeData.splice(i, 1);
break;
}
if (node.children && node.children?.length > 0) {
removeTreeItem(node.children, id);
}
}
return treeData;
};
console.log(JSON.stringify(removeTreeItem(tree, 2)));
树:批量过滤
/**
* 批量过滤出树结构满足对应字段条件的节点 (从最顶层过滤,有缺陷)
* @param {Array} treeData 要查找的源数组
* @param {functiuon} ruleVal 匹配的条件函数
*/
const filterTree = (treeData: ITree[], ruleVal: (item: ITree) => boolean) => {
var newData = treeData.filter(ruleVal);
newData.forEach(
(item) =>
item.children && (item.children = filterTree(item.children, ruleVal))
);
return newData;
};
console.log(
JSON.stringify(
filterTree(tree, (item: ITree) => item.value.indexOf("2") > -1)
)
);
树:查询一个节点
/**
*根据要查找的key返回这一项 (递归查找,引用查找)
* @param {Array} treeData 要查找的源数组
* @param {string} id 唯一标识
* @param {string} key 要查找的键名
*/
const findTreeItem = (
treeData: ITree[],
id: number,
key: keyof ITree = "id"
) => {
for (const item of treeData) {
if (item[key] === id) return item;
if (item.children && item.children.length > 0) {
let forFindRet: any = findTreeItem(item.children, id, key);
if (forFindRet) return forFindRet;
}
}
};
console.log(JSON.stringify(findTreeItem(tree, 2)));
树:增加一个节点
/**
* 添加一个节点, (没有考虑节点的关联关系)
* @param {Array} treeData 数据源
* @param {number} id 唯一标识
* @param {ITree} newObj 新数据项
*/
const addTreeItem = (treeData: ITree[], id: number | null, newObj: ITree) => {
if (!treeData || !treeData.length) {
return;
}
if (id === null) {
treeData.push(newObj);
return treeData;
}
for (let i = 0; i < treeData.length; i++) {
let node = treeData[i];
if (node.id === id) {
node.children ? node.children.push(newObj) : (node.children = [newObj]);
break;
}
if (node.children && node.children?.length > 0) {
addTreeItem(node.children, id, newObj);
}
}
return treeData;
};
console.log(
JSON.stringify(addTreeItem(tree, 5, { id: 6, pid: 5, value: "Item 5-1" }))
);
console.log(
JSON.stringify(addTreeItem(tree, 6, { id: 7, pid: 6, value: "Item 6-1" }))
);
console.log(
JSON.stringify(addTreeItem(tree, null, { id: 8, pid: null, value: "Item 3" }))
);
树:批量修改
/**
* 树:批量修改, (节点映射新字段)
* @param treeData 数据源
* @param mapRule 自定义要映射的规则
*/
const mapTree = <T>(
treeData: ITree[],
mapRule: (item: ITree) => T
): (T & ITree)[] => {
return treeData.map((item) => ({
...item,
...mapRule(item),
children:
item.children && item.children.length
? mapTree(item.children, mapRule)
: null,
}));
};
type INewField = {
newVal: string;
newField: string;
};
console.log(
JSON.stringify(
mapTree<INewField>(tree, (item) => ({
newVal: "追加字段",
newField: item.value.indexOf("1") > -1 ? "特殊标识" : "普通标识",
}))
)
);
树:查找树的最大层级
/**
* 查找树的最大层级
* @param treeData 数据源
* @returns {number}
*/
const findTreeDepth = (treeData: ITree[] | undefined): number => {
if (!treeData || treeData.length === 0) {
return 0;
}
let maxDepth = 0;
for (let node of treeData) {
const depth = findTreeDepth(node.children);
maxDepth = Math.max(maxDepth, depth);
}
// 返回子节点中最大深度加上当前节点的深度
return maxDepth + 1;
};
console.log(JSON.stringify(findTreeDepth(tree)));
总结:上述代码中
- 方便同学理解,所有的方法都是围绕上面定义的【数据示例】进行代码编写。
- 关于js 树的操作,写的都是最简单易懂的基础代码,特殊场景或特殊字段,需要自己在进行特殊改造。
有疑问的同学可以私信我、对帮助到同学欢迎大家收藏评论。