Данные приложения почти всегда имеют иерархичную структуру. Возьмем для примера список постов на Хекслете. У каждого поста есть автор, комментарии, у которых в свою очередь есть лайки. Эти данные можно представить так:

const posts = [
  {
    question: 'Как писать код?',
    likesCount: 2,
    comments: [
      {
        answer: 'Открой редактор!',
        likesCount: 1,
        createdAt: '11-12-2022',
      },
      {
        answer: 'Сидя!',
        likesCount: 3,
        createdAt: '11-12-2022',
      },
    ]
  },
  {
    question: 'Что лучше: vim или emacs?',
    likesCount: 2,
    comments: [
      {
        answer: 'FAR зе бест!',
        likesCount: 100,
        createdAt: '11-12-2022',
      },
    ]
  }
];

Иерархичное представление данных хорошо отражает их структуру. Сразу видно кто к чему относится. Их удобно выводить и вполне удобно изменять. Особенно если вывод на экране совпадает с их структурой и данные между собой не пересекаются. Топики Хекслета как раз такой пример. Каждый топик живет своей независимой жизнью (кое какие зависимости есть, но они не касаются самих данных).

Однако, если данные связаны, то иерархичная структура превращается в проблему. Представьте себе что надо выводить 10 последних комментариев. Как это сделать? Придется ходить по всем топикам, брать все комментарии, объединять и искать самые свежие. Устрашающий пример:

const comments = posts.map((p) => p.comments).flat();
const sortedComments = comments.order((c1, c2) => new Date(c2.createdAt) - new Date(c1.createdAt));
const bestComments = sortedComments.slice(0, 10);

Еще хуже ситуация становится тогда, когда появляются связи многие-ко-многим. В таком случае непонятно кого куда вкладывать. И всегда будут возникать ситуации что получившаяся структура неудобна.

Один из выходов из этой ситуации начать дублировать данные. Создавать дополнительные структуры оптимизированные под конкретные задачи. И хотя, в общем, это не лишено смысла, все же ручной способ поддерживать эти структуры ничего хорошего не принесет. В тех же базах данных, за формирование индексов отвечает сама база данных. Нам как программистам не надо об этом заботиться. А здесь придется внедрять дополнительную синхронизацию во все этапы мутаций (добавление, изменение, удаление).

Другой способ – нормализовать данные, прямо как в реляционных базах данных. Представить их плоскими массивами. Например так:

// В реальном приложении все будет храниться в одном объекте состоянии

const posts = [
  {
    id: 3,
    question: 'Как писать код?',
    likesCount: 2,
  },
  {
    id: 100
    question: 'Что лучше: vim или emacs?',
    likesCount: 2,
  }
];

const comments = [
  {
    id: 1,
    postId: 3,
    answer: 'Открой редактор!',
    likesCount: 1,
  },
  {
    id: 8,
    postId: 3,
    answer: 'Сидя!',
    likesCount: 3,
  },
  {
    id: 3,
    postId: 100,
    answer: 'FAR зе бест!',
    likesCount: 100,
  },
]

Если между данными нет четких границ и они не независимы, то такая структура намного удобнее в работе. Она легко позволяет проводить какие-то общие агрегации и особенные варианты вывода.

Но за все приходится платить. Упрощая в одном месте, она усложняет в другом. Теперь, для извлечения комментариев конкретного топика придётся написать такой код:

const postId = /* идентификатор поста */;
const commentsForPost = comments.filter((c) => c.postId === postId);

Здесь кода не больше, чем при выборке конкретного поста. Но он сложнее в алгоритмическом смысле, на него тратится больше ресурсов. Является это проблемой или нет, вопрос открытый. Как правило, нет. Фронтенд очень редко оперирует большими количествами, например, десятками и сотнями тысяч. Чаще всего размеры коллекций ограничиваются сотней-другой элементов.

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

Мы учим программированию с нуля до стажировки и работы. Попробуйте наш бесплатный курс «Введение в программирование» или полные программы обучения по Node, PHP, Python и Java.

Хекслет

Подробнее о том, почему наше обучение работает →