合并和排序对象数组 JS/TS/Svelte - 概念理解

Merge and sort arrays of objects JS/TS/Svelte - conceptual understanding

目标是显示最近的 activity 概览。

例如:我希望它显示posts、评论、用户。 post、评论和用户对象存在于其对应的数组中。所有对象都有一个时间戳(低于 createdAt),但也有来自不同数组的对象所没有的键。最近的活动应该按时间戳排序。
(最终它应该可以按不同的值进行排序,但首先我想对合并和排序数组/对象的背后有一个更好的一般理解,而不是让它变得复杂)

我想过以某种方式将数组合并成 activity 数组之类的东西,然后对其进行排序并循环遍历它,并根据对象的类型有条件地输出带有键的对象?

如果有人愿意通过举例来处理这个问题,那将让我很高兴。我能想到的最好的事情就是解决这种情况的精巧的 REPL。无论如何,我感谢每一个提示。对于我没有找到的这个(我认为常见的)用例,可能已经有很好的示例和资源。如果有人能参考一下,这也太棒了

我打算用来理解这个概念的例子:

const users = [
  { id: 'a', name: 'michael', createdAt: 1 }, 
  { id: 'b', name: 'john', createdAt: 2 },
  { id: 'c', name: 'caren', createdAt: 3 }
]
const posts = [
  { id: 'd', topic: 'food', content: 'nonomnom' createdAt: 4 },
  { id: 'e', name: 'drink', content: 'water is the best' createdAt: 5 },
  { id: 'f', name: 'sleep', content: 'i miss it' createdAt: 6 }
]
const comments = [
  { id: 'g', parent: 'd', content: 'sounds yummy' createdAt: 7 },
  { id: 'h', parent: 'e', content: 'pure life' createdAt: 8 },
  { id: 'i', parent: 'f', content: 'me too' createdAt: 9 }
]

编辑:使用更具描述性的 id 键(例如 userId 以及 post 和评论对象包含用户 ID 时,它会是一个更好的例子。但是,下面的答案使其非常易于理解并适用于“真实世界”用例。


我真的需要获得一些声望,所以我可以给你的答案点赞。感谢亲爱的人类同胞们的所有建议!

这很有趣,而且您将思想融入 activity 提要的架构中真是太好了。

我想说您的想法是正确的。

想想:

  1. 您希望如何为应用程序中使用的数据建模
  2. 您如何处理该模型
  3. 那就想想怎么展示吧

您有 3 种不同的 类型 数据,并且您有一个要创建的总体 activity 供稿。每种类型都有 createdAt 个共同点。

有几种方法可以做到这一点:

  1. 只需将它们全部合并成一个数组,然后按createdAt
  2. 排序
const activities = [...users, ...posts, ...comments];
activities.sort((a,b) => b.createdAt - a.createdAt); // Sort whichever way you want

这里棘手的部分是当你输出它时,你需要一种方法来告诉数组中的每个元素是什么类型的对象。对于用户,您可以查找 name 键,对于帖子,您可以查找 topic 键,对于评论,您可以查找 parent/content 键来确认对象类型,但这有点脆弱。

让我们试试看能不能做得更好。

  1. 给每个 activity 对象一个明确的类型变量。
const activities = [
  ...users.map((u) => ({...u, type: 'user'})),
  ...posts.map((u) => ({...u, type: 'post'})),
  ...comments.map((u) => ({...u, type: 'comment'}))
];

现在您可以根据其 type 字段轻松判断整个活动数组中的任何给定元素是什么。

作为奖励,这个 type 字段还可以让您轻松添加一项功能,将 activity 提要过滤为特定类型!而且这也使得将来添加新类型的活动变得更加简单。

这里是 typescript playground 显示它并记录输出。

作为类型安全的奖励,您可以在打字稿中添加类型以强化预期的数据类型:

例如

type User = {
  type: 'user';
  name: string;
} & Common;

type Post = {
  type: 'post';
  topic: string;
  content: string;
} & Common;

type UserComment = {
  type: 'comment';
  parent: string;
  content: string;
} & Common;

type Activity = User | Post | UserComment;

这里有一个使用简单'helper classes'的方法,以便在显示时可以区分不同的对象REPL

<script>
    class User {
        constructor(obj){
            Object.assign(this, obj)
        }
    }
    class Post {
        constructor(obj){
            Object.assign(this, obj)
        }
    }
    class Comment {
        constructor(obj){
            Object.assign(this, obj)
        }
    }

    const users = [
        { id: 'a', name: 'michael', createdAt: 1652012110220 }, 
        { id: 'b', name: 'john', createdAt: 1652006110121 },
        { id: 'c', name: 'caren', createdAt: 1652018110220 }
    ].map(user => new User(user))

    const posts = [
        { id: 'd', topic: 'food', content: 'nonomnom', createdAt: 1652016900220 },
        { id: 'e', topic: 'drink', content: 'water is the best', createdAt: 1652016910220 },
        { id: 'f', topic: 'sleep', content: 'i miss it', createdAt: 1652016960220 }
    ].map(post => new Post(post))

    const comments = [
        { id: 'g', parent: 'd', content: 'sounds yummy', createdAt: 1652116910220 },
        { id: 'h', parent: 'e', content: 'pure life', createdAt: 1652016913220 },
        { id: 'i', parent: 'f', content: 'me too', createdAt: 1652016510220 }
    ].map(comment => new Comment(comment))

    const recentActivities = users.concat(posts).concat(comments).sort((a,b) => b.createdAt - a.createdAt)  
</script>

<ul>
    {#each recentActivities as activity}        
    <li>
        {new Date(activity.createdAt).toLocaleString()} - 
            {#if activity instanceof User}
                User - {activity.name}
            {:else if activity instanceof Post}
                Post - {activity.topic}
            {:else if activity instanceof Comment}
                Comment - {activity.content}
            {/if}
    </li>
    {/each}
</ul>

为了扩展其他答案,最终您会希望以不同方式显示每个元素,同时您可以使用 if 块来测试已添加到对象的 type , 这不是很可扩展,因为一种新类型的块至少需要两个更改,一个是将类型添加到 activities 数组,另一个是将这种新类型添加到 if 块。

相反,如果我们按如下方式更改我们的活动数组:

 const activities = [
  ...users.map((u) => ({...u, component: UserCompomnent})),
  ...posts.map((u) => ({...u, component: PostComponent})),
  ...comments.map((u) => ({...u, component: CommentComponent}))
];

其中 UserComponentPostComponentCommentComponent 是表示此数据的不同方式。

然后当您遍历数据以显示它们时,我们可以使用 svelte:component 并利用我们已经定义的应该显示的组件:

{#each acitivities as activity}
  <svelte:component this={activity.component} {...activity} />
{/each}