Данные приложения почти всегда имеют иерархическую структуру. Возьмем для примера список постов на Хекслете. У каждого поста есть количество лайков и комментарии, у которых в свою очередь тоже есть лайки. Эти данные можно представить так:
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.flatMap((p) => p.comments);
const sortedComments = comments.sort((c1, c2) => new Date(c2.createdAt) - new Date(c1.createdAt));
const lastComments = 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);
Здесь кода не больше, чем при выборке конкретного поста. Но он сложнее в алгоритмическом смысле, на него тратится больше ресурсов. Является это проблемой или нет — вопрос открытый. Как правило, нет. Фронтенд очень редко оперирует большими количествами, например, десятками и сотнями тысяч. Чаще всего размеры коллекций ограничиваются сотней-другой элементов.
Подводя итог, можно сказать, что большинство механизмов для хранения состояния на фронтенде рекомендуют использовать второй способ хранения. Причем не важно, делается это в рамках какого-то фреймворка или нет. Такой подход легче масштабируется и работает неплохо для любых ситуаций. В то время как первый подход создаст много проблем в тот момент, когда структура данных перестанет совпадать с их отображением.
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.