场景1:从树中查找查找符合条件的节点(一个)
const findNodeById = (nodes, id) => {
// 遍历当前层的所有数组元素
for (const node of nodes) {
// 找到目标节点,直接返回,递归结束
if (node.id === id) {
// 这里会有两种情况:
// 1. 如果这里不是在递归中,node会作为调用处的返回值,函数结束
// 2. 如果这里是在递归中,node会作为递归调用处的返回值(赋值给foundNode的地方),当前层的递归结束
return node;
}
// 未找到目标节点,如果存在children,则将其作为nodes和原始id一起递归调用findNodeById
if (node.children) {
const foundNode = findNodeById(node.children, id);
// 找到目标节点,直接返回,递归结束
if (foundNode) {
return foundNode;
}
}
}
// 未目标节点,递归结束
return null;
};
const tree = [
{
id: '1',
name: '营销体系',
children: [
{
id: '1-1',
name: '营销体系-南区开发团队',
},
],
},
{
id: '2',
name: '交付体系',
},
];
const res = findNodeById(tree, '1-1');
console.log(res);
// {"id":"1-1","name":"营销体系-南区开发团队"}
场景2:从树中查找符合条件的节点(所有)
const getNodes = (nodes) => {
const nextNodes = [];
for (const node of nodes) {
const { children, ...rest } = node;
if (rest.value > 1) {
nextNodes.push(rest);
}
if (children) {
// 关键代码,将返回值赋值给children
nextNodes.push(...getNodes(children));
}
}
// 如果只有一级,这里返回的就是返回给外部调用处
// 如果有多级,这里会返回给nextNodes.push
return nextNodes;
};
const tree = [
{
id: '1',
name: '营销体系',
value: 1,
children: [
{
id: '1-1',
name: '营销体系-南区开发团队',
value: 2,
},
],
},
{
id: '2',
name: '交付体系',
value: 2,
},
];
const res = getNodes(tree);
console.log(res);
// [{"id":"1-1","name":"营销体系-南区开发团队","value":2},{"id":"2","name":"交付体系","value":2}]
优化一下,将识别目标的判断提取为函数依赖,提高灵活性
const getNodes = (nodes, checkIsTarget) => {
const nextNodes = [];
for (const node of nodes) {
const { children, ...rest } = node;
if (checkIsTarget(rest)) {
nextNodes.push(rest);
}
if (children) {
// 关键代码,将返回值赋值给children
nextNodes.push(...getNodes(children, checkIsTarget));
}
}
// 如果只有一级,这里返回的就是返回给外部调用处
// 如果有多级,这里会返回给nextNodes.push
return nextNodes;
};
const tree = [
{
id: '1',
name: '营销体系',
value: 1,
children: [
{
id: '1-1',
name: '营销体系-南区开发团队',
value: 2,
},
],
},
{
id: '2',
name: '交付体系',
value: 2,
},
];
const res = getNodes(tree, (node) => node.value > 1);
console.log(res);
// [{"id":"1-1","name":"营销体系-南区开发团队","value":2},{"id":"2","name":"交付体系","value":2}]
场景3:修改树中的指定节点名
修改节点名和修改节点值都可以用这种方式。只不过该方法会修改原始的树,简单的处理方式是在传入前提供tree
的副本。(后面会添加不修改原始tree
的版本。)
const updateTreeName = (nodes, currKey, targetKey) => {
for (const node of nodes) {
// 检查属性是否存在,如果存在则添加一个属性,并且把原始属性的值赋值给新属性
if (node[currKey]) {
const value = node[currKey];
node[targetKey] = value;
delete node[currKey];
}
if (node.children) {
updateTreeName(node.children, currKey, targetKey);
}
}
};
const tree = [
{
id: '1',
name: '营销体系',
children: [
{
id: '1-1',
name: '营销体系-南区开发团队',
},
],
},
{
id: '2',
name: '交付体系',
},
];
const res = updateTreeName(tree, 'name', 'title');
console.log(res);
// [{"id":"1","title":"营销体系","children":[{"id":"1-1","title":"营销体系-南区开发团队"}]},{"id":"2","title":"交付体系"}]
场景4:修改树中的指定节点名(不修改原树)
const updateTreeName = (nodes, currKey, targetKey) => {
const nextNodes = [];
for (const node of nodes) {
const { children, [currKey]: value, ...rest } = node;
// 得到原始key的值
if (currKey in node) {
rest[targetKey] = value;
}
if (children) {
// 关键代码,将返回值赋值给children
rest.children = updateTreeName(children, currKey, targetKey);
}
nextNodes.push(rest);
}
// 如果只有一级,这里返回的就是返回给外部调用处
// 如果有多级,这里返回给children
return nextNodes;
};
const tree = [
{
id: '1',
name: '营销体系',
children: [
{
id: '1-1',
name: '营销体系-南区开发团队',
},
],
},
{
id: '2',
name: '交付体系',
},
];
const res = updateTreeName(tree, 'name', 'title');
console.log(res);
// [{"id":"1","title":"营销体系","children":[{"id":"1-1","title":"营销体系-南区开发团队"}]},{"id":"2","title":"交付体系"}]
总结
递归模式分类:
模式1:不需要返回值递归
这类的递归很简单,只需要记得添加一个递归调用的条件,然后调用就行。
const recursion = (nodes) => {
for (const node of nodes) {
// do whatever you want
// 添加递归的调用条件,然后调用就好
if (node.children) {
recursion(node.children);
}
}
};
下面给出一个示例,用来修改树上的节点:
const func = (nodes, updater) => {
for (const node of nodes) {
node = updater(node);
if (node.children) {
// 要素1:递归调用
func(node.children, updater);
}
}
};
const tree = [
{
id: '1',
name: '营销体系',
value: 1,
children: [
{
id: '1-1',
name: '营销体系-南区开发团队',
value: 2,
},
],
},
{
id: '2',
name: '交付体系',
value: 2,
},
];
func(tree, (node) => {
const { name: title, ...rest } = node;
// 将name字段更名为title
return {
...rest,
title,
};
});
模式2. 获取树中符合条件的节点
这个类型的递归,相对于上面的,需要注意的地方就多了一点。
- 创建一个容器来收集目标节点
- 判断是否找到目标节点,并且放到容器中
- 返回目标容器
- 如果发生了递归,则将递归的结果也塞到容器中
const getNodes = (nodes) => {
// (1/4)
const nextNodes = [];
for (const node of nodes) {
// (2/4)
if (node.id === '1') {
nextNodes.push(node)
}
// (4/4)
if (node.chidren) {
nextNodes.push(...getNodes(item.children))
}
}
// (3/4)
return nextNodes;
};
下面给出一个示例,用于从树中递归获取目标节点集合:
const getNodes = (nodes, isTarget) => {
const nextNodes = [];
for (const node of nodes) {
if (isTarget(node)) {
nextNodes.push(node);
}
if (node.children) {
nextNodes.push(...getNodes(node.children, isTarget));
}
}
return nextNodes;
};
const tree = [
{
id: '1',
name: '营销体系',
value: 1,
children: [
{
id: '1-1',
name: '营销体系-南区开发团队',
value: 2,
},
],
},
{
id: '2',
name: '交付体系',
value: 2,
},
];
getNodes(tree, (node) => {
// 识别目标
return node.value === 2;
});
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。