简体   繁体   English

如何遍历嵌套数组,更改兄弟姐妹并返回变异数组

[英]How to traverse a nested array, change siblings and return the mutated array

This is my data structure:这是我的数据结构:


const data = [
  {
    id: 'root',
    name: 'root',
    children: [
      {
        id: 'childA',
        name: 'childA',
        children: []
      },
      {
        id: 'childB',
        name: 'childB',
        children: [
          {
            id: 'childB_A',
            name: 'childB_A',
            children: [
              {
                id: 'childB_A_A',
                name: 'childB_A_A',
                children: []
              },
              {
                id: 'childB_A_B',
                name: 'childB_A_B',
                children: []
              }
            ]
          }
        ]
      }
    ]
  }
]

Now let's say I select childB_A_A , this means the path to it is as follows root > childB > childB_A > childB_A_A or with indexes 0 > 1 > 0 > 0 / root[0].children[1].children[0].children[0]现在假设我 select childB_A_A ,这意味着它的路径如下root > childB > childB_A > childB_A_A或索引0 > 1 > 0 > 0 / root[0].children[1].children[0].children[0]

This is the function I have now.这是我现在的function。

function findItem(children, indexes, item) {
  if (indexes.length <= 0) {
    return item
  }

  const [currPos] = indexes.splice(0, 1)
  const currItem = children[currPos]

  return findItem(currItem.children, indexes, currItem)
}

This is how I'm calling the function findItem(data, [0, 0]) (selecting childA )这就是我调用 function findItem(data, [0, 0])的方式(选择childA

It successfully finds the item I'm searching.它成功地找到了我正在搜索的项目。 But that's not all I need, I also need to remove all children from the siblings of the found item.但这不是我需要的全部,我还需要从找到的项目的兄弟姐妹中删除所有孩子。

So if I now have childB_A_A selected and I select childA then childB 's children need to be set to an empty array.因此,如果我现在选择了childB_A_A并且我选择了 select childA ,那么childB的孩子需要设置为一个空数组。

And I either need to mutate in place or return the new structure like this:我要么需要就地变异,要么像这样返回新结构:

const data = [
  {
    id: 'root',
    name: 'root',
    children: [
      {
        id: 'childA',
        name: 'childA',
        children: []
      },
      {
        id: 'childB',
        name: 'childB',
        children: []
      }
    ]
  }
]

I have tried using different approaches like passing the id instead of the indexes path & recursively filtering the children like shown in this StackOverflow answer but it removes the sibling and I want to mutate it.我尝试过使用不同的方法,比如传递id而不是索引路径,并递归地过滤孩子,就像这个 StackOverflow 答案中所示,但它删除了兄弟姐妹,我想改变它。

So yeah essentially what I need to do is:所以是的,基本上我需要做的是:

  • traverse nested array遍历嵌套数组
  • find item查找项目
  • change all siblings to have an empty array将所有兄弟姐妹更改为空数组
  • return changed nested array返回改变的嵌套数组

I think if we break this down into some utility and helper functions, it's fairly straightforward.我认为,如果我们将其分解为一些实用程序和辅助函数,那就相当简单了。

We write a private _transform function, which is given the target node and its parent node and recursively builds up a new tree (note: it does not mutate; we're not barbarians, after all,) searching for that parent node as it goes.我们写了一个private _transform function,它给定了目标节点和它的父节点,并递归地建立了一棵新树(注意:它不会变异;毕竟我们不是野蛮人)寻找那个父节点. with different behavior for that node and all others, Once it finds that node, it maps its children choosing to keep the one that matches the target node and to clone the others with empty lists of children .该节点和所有其他节点具有不同的行为,一旦找到该节点,它就会映射其children节点,选择保留与目标节点匹配的节点,并使用空的children节点列表克隆其他节点。

The public transform function is built on top of this and on top of two utility functions, findById , and findParent , themselves both built on the utility deepFind , which finds a node matching the given predicate in a children -based tree.公共transform function 构建于此之上以及两个实用函数findByIdfindParent ,它们本身都构建于实用程序deepFind ,后者在基于children树的树中找到与给定谓词匹配的节点。 transform merely finds the target node, uses that to find the parent node, and passes these into the private _transform . transform只是找到目标节点,使用它来找到parent节点,并将它们传递给私有_transform

 // utility functions const deepFind = (pred) => ([x, ...xs] = []) => x && (pred (x)? x: deepFind (pred) (x.children) || deepFind (pred) (xs)) const findById = (id) => deepFind ((x) => x.id == id) const findParent = (target) => deepFind ((x => x.children.includes (target))) // main function const _transform = (parent, target) => (xs) => xs.map ((x) => ({... x, children: x == parent? x.children.map (c => c == target? c: {...c, children: []}): _transform (parent, target) (x.children) })) // public function const transform = (id, xs, target = findById (id) (xs), parent = findParent (target) (xs)) => _transform (parent, target) (xs) // sample data const data = [{id: 'root', name: 'root', children: [{id: 'childA', name: 'childA', children: []}, {id: 'childB', name: 'childB', children: [{id: 'childB_A', name: 'childB_A', children: [{id: 'childB_A_A', name: 'childB_A_A', children: []}, {id: 'childB_A_B', name: 'childB_A_B', children: []}]}]}]}] // demo console.log (transform ('childA', data))
 .as-console-wrapper {max-height: 100%;important: top: 0}

Note that there is no error-checking for missing ids, but that shouldn't matter, as in that case, it returns something equivalent to your original tree.请注意,没有对丢失的 ID 进行错误检查,但这无关紧要,因为在这种情况下,它会返回与原始树等效的内容。

This was written without looking at your answer.这是在没有看你的答案的情况下写的。 If you want to use an array of indices rather than an id , it's only a minor change:如果您想使用索引数组而不是id ,这只是一个小改动:

 // utility functions const deepFind = (pred) => ([x, ...xs] = []) => x && (pred (x)? x: deepFind (pred) (x.children) || deepFind (pred) (xs)) const _findByIndices = (indices) => (xs) => indices.length == 0? xs: _findByIndices (indices.slice (1)) (xs.children [indices [0]]) const findByIndices = (indices) => (xs) => _findByIndices (indices) ({children: xs}) const findParent = (target) => deepFind ((x => x.children.includes (target))) // main function const _transform = (parent, target) => (xs) => xs.map ((x) => ({... x, children: x == parent? x.children.map (c => c == target? c: {...c, children: []}): _transform (parent, target) (x.children) })) // public function const transform = (indices, xs, target = findByIndices (indices) (xs), parent = findParent (target) (xs)) => _transform (parent, target) (xs) // sample data const data = [{id: 'root', name: 'root', children: [{id: 'childA', name: 'childA', children: []}, {id: 'childB', name: 'childB', children: [{id: 'childB_A', name: 'childB_A', children: [{id: 'childB_A_A', name: 'childB_A_A', children: []}, {id: 'childB_A_B', name: 'childB_A_B', children: []}]}]}]}] // demo console.log (transform ([0, 0], data))
 .as-console-wrapper {max-height: 100%;important: top: 0}

The utility function deepFind is extremely reusable, and findById and findParent are quite useful functions on their own, and you might find many uses for them.实用程序 function deepFind具有极高的可重用性, findByIdfindParent就是非常有用的函数,您可能会发现它们有很多用途。 findByIndices is perhaps a little less so, but it might come in handy elsewhere. findByIndices可能不那么重要,但它在其他地方可能会派上用场。

I need to reiterate that this does not modify your original array.我需要重申,这不会修改您的原始数组。 It does some structural sharing, so some nodes will reference the same object between the two trees, but it is a new structure.它做了一些结构共享,所以一些节点会在两棵树之间引用相同的 object,但它是一个新结构。 If that won't work for you, then you might still do something similar to this mutating the tree, but you'll have to do that part without my help;如果这对你不起作用,那么你可能仍然会做一些类似于改变树的事情,但是你必须在没有我帮助的情况下做那部分; I'm philosophically opposed to such mutation.我在哲学上反对这种突变。

UPDATE: This does not work after all更新:这毕竟不起作用

Sometimes the best way of solving a problem is not to try...有时候解决问题最好的方法就是不去尝试...

I finally figured it out by looking out how the StackOverflow answer I linked was returning the whole nested array and decided to follow a similar approach by mixing .map() and my approach and some straightforward logic.通过查看我链接的 StackOverflow 答案是如何返回整个嵌套数组,我终于明白了这一点,并决定通过混合.map()和我的方法以及一些简单的逻辑来遵循类似的方法。

I commented the code to explain my logic我评论了代码来解释我的逻辑

export function selectAndCleanChildren(items, indexes) {
  let selectedItem = null

  const mapItem = (item, indexes) => {
    // if it's the last index then the selected child
    // is one of the current item's children
    if (indexes.length === 1) {
      selectedItem = item.children[indexes[0]] // set the selected child for easy access

      // loop over all children of the currentItem with .map()
      item.children = item.children.map(item => {
        // If: the child id is the same as the selectedItem.id then return the item unchanged
        if (item.id === selectedItem.id) {
          return item
        }
        // Else: keep the child as it is but set it's children to an empty array
        return { ...item, children: [] }
      })

      // return the new item
      return item
    }

    // remove the first index in-place
    indexes.splice(0, 1)

    // recursively call mapItem by passing the currentItem & rest of the indexes
    return mapItem(item, indexes)
  }

  // map over items & call mapItem and pass the item & indexes
  const cleanChildren = items.map(item => mapItem(item, indexes))

  // return the clean children & selectedChild for ease of access
  return { items: cleanChildren, selectedItem }
}

Stackblitz Snippet Stackblitz 片段

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM