有条件地递归扩展对象(嵌套子对象)

Conditionally expanding an Object recursively (nested children)

这样做的目的是让评论具有递归子评论。

作为此示例数据,有已获取的帖子和评论。

let posts = [
    { id: '001a', topic: 'post topic', content: 'post content' }
]
let comments = [
    { id: '002a', postParent: '001a', directParent: '001a', content: 'comment on post' },
    { id: '003a', postParent: '001a', directParent: '002a', content: 'comment on comment' },
    { id: '004a', postParent: '001a', directParent: '003a', content: 'comment on comments comment' },
]

如果我要用文字表达的话,我会说:有条件地以评论对象父键为条件,这个对象应该成为其父评论对象的子评论对象。 为了实现这一点,我开始认为它需要一个功能来创建另一个数组。喜欢:

let postComments = [
    {
        id: '002a',
        postParent: '001a',
        directParent: '001a',
        content: 'comment on post',
        children: [
            { 
                id: '003a', 
                postParent: '001a', 
                directParent: '002a', 
                content: 'comment on comment',
                children: [ {id: '004a', post: '001a', parent: '003a', content: 'comment on comments comment' }} ]
            }
        ]
    },
]

我到目前为止尝试的方法是在不创建这样一个扩展自身的新数组的情况下解决这个问题。 我尝试在每个块中有条件地输出对它的父项的评论。不幸的是,这并没有导致可扩展的解决方案(/以递归方式进行 -我不确定这里的术语是否正确)。 这就是我如何找到这个问题现在所涉及的方法以及如何创建一个新数组,如示例中所示。

但是我仍然不确定在拥有此类数据时它是否是一个好的解决方案,非常欢迎任何关于此类问题的行之有效的方法的建议。

不幸的是,在查看和保存另一个 REPL 时,我幸运地能够根据提供的答案创建的大量 REPL 被覆盖了。我不得不删除现在具有误导性的 link,但如果有一些工作涉及到它,那么小的收获就是备份 svelte REPLS。我没有。不管怎样,下面的答案有足够好的例子。

let posts = [{ id: '001a', topic: 'post topic', content: 'post content' }]
let comments = [
  { id: '002a', post: '001a', parent: '001a', content: 'comment on post' },
  { id: '003a', post: '001a', parent: '002a', content: 'comment on comment' },
  { id: '004a', post: '001a', parent: '003a', content: 'comment on comments comment' },
]

function getNestedComments(parentId) {
  const subComments = comments.filter((c) => c.parent === parentId)
  if (subComments.length === 0) return subComments
  subComments.forEach(c => {
    c.children = getNestedComments(c.id)
  })
  return subComments
}

let postComments = getNestedComments('001a')
console.log(postComments)

TypeScript Playground

这是另一个没有递归函数的解决方案。它使用 map-lookup.

let posts = [
    { id: '001a', topic: 'post topic', content: 'post content' }
]
let comments = [
    { id: '002a', post: '001a', parent: '001a', content: 'comment on post' },
    { id: '003a', post: '001a', parent: '002a', content: 'comment on comment' },
    { id: '004a', post: '001a', parent: '003a', content: 'comment on comments comment' }
]

let postComments = toNestedComments(comments, posts[0].id);
console.log(postComments);

function toNestedComments(comments, postId) {
    const nestedComments = [];
    const map = {};

    for (let i = 0; i < comments.length; i++) {
        map[comments[i].id] = i;
    }

    for (let comment of comments) {
        if (comment.parent === postId) {
            nestedComments.push(comment);
        } else {
            if (!comments[map[comment.parent]].hasOwnProperty('children')) {
                comments[map[comment.parent]].children = [];
            }
            comments[map[comment.parent]].children.push(comment);
        }
    }

    return nestedComments;
}

仅当您呈现 parent 时检索属于特定 parent 的评论在我看来效率低下,因为每次您尝试呈现代码时都会 运行 到 所有你的评论,以找到属于那个parent的评论。

更好的方法(在我看来)是向每个 posts/comment 添加一个 属性 children 并填充它,就像您在代码中所做的那样。这里的难点在于如何以最小化对所有数据进行 'loop' 的次数的方式进行此操作。

一种方法是为每个 post 构建一个查找 table 并像这样评论:

const lookup = [...posts, ...comments].reduce((pre, cur) => {
  cur.children = []; // Prepares an array to hold the children
  pre[cur.id] = cur;
  return pre;
}, {});

然后再次遍历评论,将它们添加到 parent 我们可以从查找 table.

中读取
comments.forEach(c => lookup[c.parent].children.push(c));

由于 javascript 相对于 objects 的工作方式,这将导致原始 postscomments 的项目现在具有 children 属性 填充了该元素的评论(这些评论有自己的 children)。

现在您可以使用 Post 组件和 svelte:self.

递归渲染它们
{#each children as child}
  <svelte:self {...child} />
{/each}

此解决方案仅通过两次数据来构建此构造(一次进行查找 table,一次附加评论)因此比在渲染过程中搜索评论更有效。

如果您的数据排序方式使得评论总是出现在parent之后,您可以在构建地图的同时推送到 parent 的 children 会更好:

let map = [...posts, ...comments].reduce((pre, cur) => {
  cur.children = [];
  pre[cur.id] = cur;
  if (cur.parent) pre[cur.parent].children.push(cur);
  return pre;
}, {});

编辑以适应代码段中提供的数据

const lookup = [...posts].reduce((pre, cur) => {
  // Either a post or a comment
  const item = cur.post ?? cur.comment;
  // Prepares an array to hold the children
  item.children = []; 
  pre[cur.publicKey] = item;
  return pre;
}, {});
    
comments.forEach(({ comment }) => lookup[comment.post].children.push(comment));

不完美,但开始有了想法。这会将所有评论放在 post 上,因此评论没有自己的 children.

Demo Repl