如何以程序方式将新的子对象添加到深度嵌套的对象中



考虑以下对象,该对象由数量未知的深度嵌套子对象组成。

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.assignArray#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)

最新更新