Слияние и сортировка массивов объектов JS/TS/Svelte — концептуальное понимание

Цель состоит в том, чтобы отобразить обзор последних действий.

Как пример: хотелось бы, чтобы отображались посты, комментарии, пользователи. Пост, комментарий и пользовательский объект живут в соответствующих им массивах. Все объекты имеют метку времени (ниже createdAt), а также ключи, которых нет у объектов из разных массивов. Недавние действия должны быть отсортированы по отметке времени.
(Ultimately it should be sortable by different values, but first I would like to get a better general understanding behind merging and sorting arrays / objects and not making it to complicated)

Я подумал о том, чтобы каким-то образом объединить массивы во что-то вроде массива действий, затем отсортировать его и зациклиться на нем и условно вывести объект с его ключами в зависимости от того, какой это объект?

Если кто-то готов разобраться с этим, приведя пример, это сделает мой день. Лучшее, что я мог себе представить, это стройный 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, и когда объект сообщения и комментария содержит идентификатор пользователя. Тем не менее, приведенные ниже ответы делают его очень понятным и применимым для случаев использования в «реальном мире».


Мне действительно нужно получить некоторую репутацию, чтобы я мог голосовать за ваши ответы. Спасибо за все ваши советы, дорогие земляки!

Структурированный массив Numpy
Структурированный массив Numpy
Однако в реальных проектах я чаще всего имею дело со списками, состоящими из нескольких типов данных. Как мы можем использовать массивы numpy, чтобы...
T - 1Bits: Генерация последовательного массива
T - 1Bits: Генерация последовательного массива
По мере того, как мы пишем все больше кода, мы привыкаем к определенным способам действий. То тут, то там мы находим код, который заставляет нас...
Что такое деструктуризация массива в JavaScript?
Что такое деструктуризация массива в JavaScript?
Деструктуризация позволяет распаковывать значения из массивов и добавлять их в отдельные переменные.
0
0
67
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Об этом интересно думать, и здорово, что вы продумываете архитектуру ленты активности.

Я бы сказал, что вы на правильном пути с тем, как вы думаете о подходе к этому.

Подумай о:

  1. Как вы хотите смоделировать данные для использования в вашем приложении
  2. Как вы обрабатываете эту модель
  3. Затем подумайте, как вы это покажете.

У вас есть 3 разных типы данных, и у вас есть общая лента активности, которую вы хотите создать. У каждого типа есть createdAt общего.

Есть несколько способов сделать это:

  1. Просто объедините их все в один массив, а затем отсортируйте по createdAt.
const activities = [...users, ...posts, ...comments];
activities.sort((a,b) => b.createdAt - a.createdAt); // Sort whichever way you want

Сложность здесь заключается в том, что при выводе вам понадобится способ указать, к какому типу объекта относится каждый элемент массива. Для пользователей вы можете искать ключ name, для сообщений вы можете искать ключ topic, для комментариев вы можете искать ключи parent/content для подтверждения типа объекта, но это немного хрупкий подход.

Давайте попробуем посмотреть, сможем ли мы сделать лучше.

  1. Назначьте каждому объекту действия явную переменную типа.
const activities = [
  ...users.map((u) => ({...u, type: 'user'})),
  ...posts.map((u) => ({...u, type: 'post'})),
  ...comments.map((u) => ({...u, type: 'comment'}))
];

Теперь вы можете легко сказать, что любой данный элемент во всем массиве действий основан на его поле type.

В качестве бонуса это поле type также позволяет вам легко добавить функцию для фильтрации ленты активности только по определенным типам! И это также значительно упрощает добавление новых видов деятельности в будущем.

Вот машинописная площадка, показывающий это и регистрирующий вывод.

В качестве бонуса за безопасность типов вы можете добавлять типы в машинописный текст, чтобы усилить ожидаемые типы данных:

например.

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;

Это потрясающе! Большое спасибо! Я понял, что пример, который я привел, немного ошибочен, но, насколько я могу думать с точки зрения моего вопроса, этот ответ помогает понять все, что я искал! Спасибо еще раз

Truiiya 08.05.2022 16:16

Типы могут быть улучшены, чтобы включать type в качестве литералов, что позволяет выполнять сужение типа с использованием этого свойства (Детская площадка).

H.B. 08.05.2022 17:56

ах, упс, забыл включить поле type! Спасибо ХБ. @Truiiya, если вы нашли ответ полезным / в конечном итоге применили его, рассмотрите возможность пометить его как принятый!

Azarro 08.05.2022 19:43

Да спасибо. Как только я сяду за эту часть проекта на этой неделе, я попробую их, приложу все усилия, чтобы действительно понять их, и отмечу тот, который я решил использовать, как принятый. Также проголосуйте за все эти хорошие ответы, как только я смогу. Я узнал, что благодарить в комментариях - это не то, что нужно делать, а скорее голосовать и принимать ответы.

Truiiya 09.05.2022 15:23

@Truiiya только что увидела, что вы отметили это, удачи в вашем проекте!

Azarro 14.05.2022 19:32

Вот подход, использующий простые «вспомогательные классы», чтобы можно было различать разные объекты при отображении РЕПЛ

<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>

Я бы не рекомендовал полагаться на constructor.name, потому что минификаторы могут исказить их при сборке. Если вы все равно используете классы, вы можете просто использовать, например. activity instanceof User вместо этого.

H.B. 08.05.2022 17:44

@Х.Б. Спасибо за совет! Изменил это...

Corrl 08.05.2022 18:18

Чтобы расширить другие ответы, в конечном итоге вы захотите показать каждый элемент также по-разному, хотя вы можете сделать это с помощью блока if, тестирующего type, который был добавлен к объекту, это не очень масштабируемо, поскольку новый тип блока требуется как минимум два изменения: одно для добавления типа в массив activities и одно для добавления этого нового типа в блоки if.

Вместо этого, если мы изменим наш массив действий следующим образом:

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

где UserComponent, PostComponent и CommentComponent — различные способы представления этих данных.

Затем, когда вы перебираете свои данные для их отображения, мы можем использовать svelte:component и использовать то, что мы уже определили, какой компонент должен отображаться:

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

Другие вопросы по теме