Зарегистрируйтесь, чтобы продолжить обучение

Нормализация данных JS: Архитектура фронтенда

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

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

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

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


Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 25 000 ₸ в месяц
Разработка фронтенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 26 декабря
профессия
от 39 525 ₸ в месяц
Разработка фронтенд- и бэкенд-компонентов для веб-приложений
16 месяцев
с нуля
Старт 26 декабря

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»