【问题标题】:How does one, in a nested tree like data-structure, add child-nodes to a parent-node's children-array by a parent-node's id?在像数据结构这样的嵌套树中,如何通过父节点的 id 将子节点添加到父节点的子数组中?
【发布时间】:2021-05-14 00:59:18
【问题描述】:

我有一个 json 对象:

const data = {
  id: 'root',
  name: 'Parent',
  children: [
    {
      id: '1',
      name: 'Child - 1',
    },
    {
      id: '3',
      name: 'Child - 3',
      children: [
        {
          id: '4',
          name: 'Child - 4',
        },
      ],
    },
  ],
};

我想通过 id 将新的子数组动态添加到任何父元素。例如。我想找到 id='4' 的元素并添加子元素:[{ id:'5', name: 'Child - 4' }]

【问题讨论】:

标签: javascript arrays recursion data-structures iteration


【解决方案1】:

id 匹配时,您可以构建一个递归函数将数据添加到子数组中。

const data = [{ id: 'root', name: 'Parent', children: [ { id: '1', name: 'Child - 1', }, { id: '3', name: 'Child - 3', children: [ { id: '4', name: 'Child - 4', }, ], }, ],}];

const addDataToId=(arr, id, children)=>{
    arr.forEach(i=>{
        if(i.id==id){
           i.children = [...(i.children || []), ...children];
        } else {
            addDataToId(i.children|| [], id, children)
        }
    })
}

addDataToId(data, 4, [{ id:'5', name: 'Child - 4' }]);

console.log(data);

【讨论】:

    【解决方案2】:

    正如对 OP 问题的评论中已经建议的那样,可以选择一种递归方法,通过 Array.prototype.some 在内部迭代父节点的 children 数组,以实现提前退出(在数组迭代时)和指示是否可以通过其布尔返回值添加 (a) 子节点(如果目标父节点存在)。

    还必须注意可能的子节点重复(通过他们的ids)以及如何处理它们。

    // E.g. I want to find element with id='4'
    // and add children: [{ id:'5', name: 'Child - 4' }]
    
    const data = {
      id: 'root',
      name: 'Parent',
      children: [
        {
          id: '1',
          name: 'Child - 1',
        },
        {
          id: '3',
          name: 'Child - 3',
          children: [
            {
              id: '4',
              name: 'Child - 4',
            },
          ],
        },
      ],
    };
    
    function addChildrenToParentByParentId(id, node, childNodes) {
      let isSuccess = false;
      
      const { children } = node;
      if (node.id === id) {
    
        // because `childNodes` can be both a single node or a node list.
        childNodes = [].concat(childNodes);
    
        // prevent child node duplicates by ...
        node.children =
          ((Array.isArray(children) && children) || []).filter(child =>
          
            // ... skipping older nodes which feature newer ids ...
            childNodes.every(node => node.id !== child.id)
    
          // ... and always concatenating the most recent child nodes.
          ).concat(childNodes);
    
        isSuccess = true;
    
      } else if (Array.isArray(children)) {
    
        // recursion ... but using `Array.prototype.some`
        // in order to achieve both an early exit and the
        // indication whether children could be added.
    
        isSuccess = children.some(child =>
          addChildrenToParentByParentId(id, child, childNodes)
        );
      }
      return isSuccess;
    }
    
    // fails ... provides a node list
    console.log(
      "addChildrenToParentByParentId('2', data, [{ id:'5', name: 'Child - 4' }]) ...",
      addChildrenToParentByParentId('2', data, [{ id:'5', name: 'Child - 4' }])
    );
    console.log(data);
    
    // succeeds ... provides a single node
    console.log(
      "addChildrenToParentByParentId('4', data, { id:'5', name: 'Child - 4' }) ...",
      addChildrenToParentByParentId('4', data, { id:'5', name: 'Child - 4' })
    );
    console.log(data);
    
    // succeeds ... provides a node list
    console.log(
      "addChildrenToParentByParentId('3', data, [{ id:'6', name: 'Child - 4' }, { id:'7', name: 'Child - 4' }]) ...",
      addChildrenToParentByParentId('3', data, [{ id:'6', name: 'Child - 4' }, { id:'7', name: 'Child - 4' }])
    );
    console.log(data);
    
    // succeeds ... provides a single node ... prevents duplicates
    console.log(
      "addChildrenToParentByParentId('3', data, { id:'6', name: 'Child - XX' }) ...",
      addChildrenToParentByParentId('3', data, { id:'6', name: 'Child - XX' })
    );
    console.log(data);
    .as-console-wrapper { min-height: 100%!important; top: 0; }

    【讨论】:

      【解决方案3】:

      我更喜欢通过将树遍历从测试是否在当前节点上工作的谓词和更新节点的转换函数中分离出来来编写这样的函数。

      这是一个带有 alterTree 函数的版本,它执行树遍历,接受谓词函数和转换函数并返回一个函数,该函数接受一个对象(.children 数组保持树结构)并返回一个通过调用转换函数来转换与谓词匹配的所有节点并保持其他节点不变:

      const alterTree = (pred, trans) => (o) =>
        pred (o)
          ? trans (o)
          : {...o, ...(o .children ? {children: o .children .map (alterTree (pred, trans))} : {})}
      
      const addChildren = (targetId, children) =>
        alterTree (
          ({id}) => id === targetId,
          (node) => ({...node, children: [... (node .children || []), ... children]})
        )
      
      
      const data = {id: 'root', name: 'Parent', children: [{id: '1', name: 'Child - 1', }, {id: '3', name: 'Child - 3', children: [{id: '4', name: 'Child - 4'}]}]};
      
      console .log (addChildren ('4', [{id: '5', name: 'Child - 5'}]) (data))
      .as-console-wrapper {max-height: 100% !important; top: 0}

      我们在addChildren 的定义中调用alterTree,传递一个报告id 是否与我们的目标匹配的函数和一个添加节点的第二个函数。

      请注意,alterTree 的工作是不变的。但是,如果您传递给它的函数改变了它们的输入,则无法保证我们不会改变您的原始对象。 addChildren 传递的那些,但是不改变任何东西,所以这个函数也不会改变原始数据。它返回应用了更改的对象的新副本。

      如果我们愿意,我们可以更进一步,将 "children" 的使用抽象为我们的后代节点,但这有点离题了。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-03-20
        • 2021-07-16
        • 1970-01-01
        • 2020-02-27
        • 2020-08-25
        • 2019-09-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多