开发者问题收集

无法递归删除对象数组中的对象

2021-04-21
153
const arr = [
  {
    id: "8bfc-2b82-68ad-fb78",
    name: "inventory",
    type: "directory",
    path: "inventory/",
    children: [
      {
        id: "bccf-3788-6ec9-ba33",
        name: "inventory.yaml",
        type: "file",
        path: "inventory/inventory.yaml",
      },
    ],
  },
  {
    id: "4a28-4d63-6c30-4569",
    name: "model",
    type: "directory",
    path: "model/",
    children: [
      {
        id: "21d1-f4d2-e1c5-34b0",
        name: "model.yaml",
        type: "file",
        path: "model/model.yaml",
      },
    ],
  },
];








  function _worktreeItemDelete({ detail }) {
    const currentId = detail.item.id;
    const filtered = this.tree.filter(entry => entry.id !== currentId);
    const updTree = filtered.map(entry => {
      if (!entry.children) return entry;
      return {...entry, children: this._worktreeItemDelete(entry.children, currentId)};
    });
    console.log(updTree);
    this.set("tree", updTree);
  }

这就是我正在做的。 currentId 我正在作为事件属性值捕获。 我现在遇到的问题是:

Uncaught RangeError: Maximum call stack size exceeded

如果我理解正确的话,递归不会停止,正在寻找一种方法来处理这个问题。 感谢您的帮助。

3个回答
function removeById(arr, id) {
  return arr.filter(item => {
    arr.children = arr.children && removeById(arr.children);
    return item.id !== id;
  });
}

您可以在代码中使用此辅助函数

const filtered = removeById(this.tree, currentId);
Ziarno
2021-04-23

这是使用 object-scan 的迭代解决方案。使用库来处理您的用例可能有点过头了,但如果需要的话,它可能会使维护该功能变得容易得多。

例如,调整它以允许删除非唯一 ID 只需要删除中止标志。

// const objectScan = require('object-scan');

const myArr = [{ id: '8bfc-2b82-68ad-fb78', name: 'inventory', type: 'directory', path: 'inventory/', children: [{ id: 'bccf-3788-6ec9-ba33', name: 'inventory.yaml', type: 'file', path: 'inventory/inventory.yaml' }] }, { id: '4a28-4d63-6c30-4569', name: 'model', type: 'directory', path: 'model/', children: [{ id: '21d1-f4d2-e1c5-34b0', name: 'model.yaml', type: 'file', path: 'model/model.yaml' }] }];

const removeById = (arr, id) => objectScan(['**(^children$).id'], {
  useArraySelector: false,
  filterFn: ({ value, gparent, gproperty }) => {
    if (value === id) {
      gparent.splice(gproperty, 1);
      return true;
    }
    return false;
  },
  abort: true,
  rtn: 'bool'
})(arr);

console.log(removeById(myArr, '21d1-f4d2-e1c5-34b0'));
// => true

console.log(myArr);
/* =>
[
  {
    id: '8bfc-2b82-68ad-fb78',
    name: 'inventory',
    type: 'directory',
    path: 'inventory/',
    children: [
      {
        id: 'bccf-3788-6ec9-ba33',
        name: 'inventory.yaml',
        type: 'file',
        path: 'inventory/inventory.yaml'
      }
    ]
  },
  {
    id: '4a28-4d63-6c30-4569',
    name: 'model',
    type: 'directory',
    path: 'model/',
    children: []
  }
]
*/
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/[email protected]"></script>

免责声明 :我是 object-scan

的作者
vincent
2021-04-26

修复函数

您已经接近了。问题在于,您将一个对象传递给初始删除调用,提取嵌套属性 id 以便正确使用,但随后在进行递归调用时,您只使用该 id,而不是整个对象。

最快的修复方法可能是保留对该对象的引用,而不是在参数中对其进行解构。我对上下文的了解还不够,无法正确命名,所以我就将其称为 foo 。然后我们可以将 foo 传递给递归调用,它就会正常工作。

修复后,您的函数变体将正常工作:

function _worktreeItemDelete(tree, foo) {
  const currentId = foo.detail.item.id;
  const filtered = tree.filter(entry => entry.id !== currentId);
  const updTree = filtered.map(entry => {
    if (!entry.children) return entry;
    return {...entry, children: _worktreeItemDelete(entry.children, foo)};
  });
  return updTree
}

const arr = [{id: "8bfc-2b82-68ad-fb78", name: "inventory", type: "directory", path: "inventory/", children: [{id: "bccf-3788-6ec9-ba33", name: "inventory.yaml", type: "file", path: "inventory/inventory.yaml", }]}, {id: "4a28-4d63-6c30-4569", name: "model", type: "directory", path: "model/", children: [{id: "21d1-f4d2-e1c5-34b0", name: "model.yaml", type: "file", path: "model/model.yaml"}]}]

console .log (
  _worktreeItemDelete (arr, {detail: {item: {id: 'bccf-3788-6ec9-ba33'}}})
)
.as-console-wrapper {max-height: 100% !important; top: 0}

请注意,我将其更改为纯函数而不是方法,因此跳过了 this.set("tree", updTree) ,添加了 tree 参数,并删除了 this 引用。它们应该很容易放回去。

但是 ,以上内容并未解释您描述的递归深度错误。我期望更像这样的内容

Cannot read property 'item' of undefined

因此可能会发生其他事情。

替代方法

我会以不同的方式编写此代码。我将使用通用版本的树过滤函数,并向其传递节点选择的谓词。

我的版本可能如下所示:

const deepFilter = (pred) => (xs) =>
  xs .flatMap (({children = [], ...rest}) => 
    pred (rest)
      ? [{... rest, children: deepFilter (pred) (children)}]
      : []
  )

const removeById = ({detail: {item: {id: target}}}) => 
  deepFilter (({id}) => id !== target)

const arr = [{id: "8bfc-2b82-68ad-fb78", name: "inventory", type: "directory", path: "inventory/", children: [{id: "bccf-3788-6ec9-ba33", name: "inventory.yaml", type: "file", path: "inventory/inventory.yaml", }]}, {id: "4a28-4d63-6c30-4569", name: "model", type: "directory", path: "model/", children: [{id: "21d1-f4d2-e1c5-34b0", name: "model.yaml", type: "file", path: "model/model.yaml"}]}]

console .log (
  removeById ({detail: {item: {id: 'bccf-3788-6ec9-ba33'}}}) (arr)
)
.as-console-wrapper {max-height: 100% !important; top: 0}

请注意, deepFilter 是通用的。它适用于后代节点位于属性 children 中的任何树。(请参阅 另一个答案 ,了解允许您配置此属性的更通用的版本。)

因此,此处的自定义代码只是 removeById 。在我看来,这是一个不错的胜利。

如果它很重要,我们可以——以增加一点复杂性为代价——选择仅在数据不为空时包含 children 节点。如果您想要这样做并需要帮助,只需添加评论即可。

Scott Sauyet
2021-04-23