考虑以下对象,该对象由数量未知的深度嵌套子对象组成。
const state = {
id: 1,
children: [
{
id: 3,
children: [
{
id: 4,
children: []
}
]
},
{
id: 2,
children: []
}
]
}
如何通过程序将一个新对象推送到节点的children
数组,而只知道它的id
和它的父对象的id
数组?我曾想过使用递归,但找不到有效的解决方案。我也在使用不变性助手,所以我尝试使用Array.reduce()
返回一个看起来像这样的对象:
const newState = {
children: {
[idxOfNodeToChange]: {
children: {$push: newChildren}
}
}
}
所以我可以把它传给update()
,但在那里我更卡住了,因为我每次都必须穿过累加器才能达到所需的深度,我不知道如何做到这一点。有什么想法吗?
额外信息:我正在为React使用一个名为VX的D3库,这个结构是构建树组件所必需的,所以我一直纠结于如何用程序添加新节点。
开始吧。您可以使用此递归函数按id进行搜索,并将数据附加到找到的节点的子数组中。
function appendChildToNode(node, nodeId, data) {
// If the node is empty, just return it as is.
if (!node) {
return node;
}
let children;
if (node.id === nodeId) {
// If the node has the id we're searching for,
// append the data to its children.
children = [...node.children, data];
} else {
// Otherwise, apply the function recursively to each of its children
children = node.children.map(childNode => appendChildToNode(childNode, nodeId, data));
}
return { ...node, children };
}
它是不可变的,你可以这样使用它:
const newState1 = appendChildToNode(state, 4, { id: 5, children: [] });
const newState2 = appendChildToNode(state, 2, { id: 5, children: [] });
请参阅下面的示例片段
const state = {
id: 1,
children: [{
id: 3,
children: [{
id: 4,
children: []
}]
},
{
id: 2,
children: []
}
]
};
const newState1 = appendChildToNode(state, 4, {
id: 5,
children: []
});
const newState2 = appendChildToNode(state, 2, {
id: 5,
children: []
});
console.log(state); // Orginal state should not be mutated.
console.log(newState1);
console.log(newState2);
function appendChildToNode(node, nodeId, data) {
// If the node is empty, just return it as is.
if (!node) {
return node;
}
let children;
if (node.id === nodeId) {
// If the node has the id we're searching for,
// append the data to its children.
children = [...node.children, data];
} else {
// Otherwise, apply the function recursively to each of its children
children = node.children.map(childNode => appendChildToNode(childNode, nodeId, data));
}
return { ...node, children };
}
更新:以上函数使用ES6排列语法来附加项。如果您需要支持不带转译的旧浏览器,可以使用Object.assign
和Array#concat
使用此更新版本。
function appendChildToNode(node, nodeId, data) {
if (!node) {
return node;
}
var children;
if (node.id === nodeId) {
children = node.children.concat([data]);
} else {
children = node.children.map(childNode => appendChildToNode(childNode, nodeId, data));
}
return Object.assign({}, node, { children });
}
让我们在我们的状态中找到ID为3的节点,深入搜索一级。取当前节点的子节点,在这些子节点中找到ID正确的节点:
id = 3
node3 = state.children.find(v => v == id)
在该节点中,找到ID 4。现在我们正在节点3的子节点中搜索:
id = 4 // ┌ act on node3!
node4 = node3.children.find(v => v == id)
根据Array.reduce()
,累加器是当前节点。它从根节点state
开始,然后向下遍历树:每次,我们都使用ID列表中的下一个ID遍历树一级。我们需要递归从树的根开始,所以初始值是我们的根节点state
。
如果我们拿上面的例子和reduce
他们:
[3, 4].reduce((acc, x) => acc.children.find(v => v === x), state)
// ids traverse one level start at root node
展开它,这相当于:
(state.children.find(v => v === 3)).children.find(v => v === 4)
一般形式变为:
const recursiveTraversal = ids =>
ids.reduce((acc, x) => acc.children.find(v => v === x), state)