Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос нашим менторам. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.
Об обучении на Хекслете

Библиотека Reselect

В курсе по React мы немного обсуждали вопросы производительности. Неправильная работа с данными может легко привести к серьёзным просадкам производительности. Об этом нужно помнить и за этим надо следить. Redux нас не просто не избавляет от этого, но и добавляет своих особенностей (хотя кое-какие оптимизации внутри уже встроены).

Для уменьшения количества "холостых" (когда ничего не изменилось) перерисовок виртуального DOM, важно следить за тем, как пишется функция mapStateToProps:

  • Нужно стараться передавать как можно меньше данных (но не меньше, чем нужно).
  • Нужно избегать изменений в mapStateToProps.

Функция mapStateToProps выполняется всегда, когда запускается процесс рендеринга компонента, даже если его данные не изменились (но изменились для каких-то других компонентов в этой же части дерева компонентов). Это приводит к ненужным и иногда тяжёлым вычислениям.

Последнее рассмотрим подробнее. Напомню наш код из практики:

const mapStateToProps = ({ tasks }) => {
  const props = {
    tasks: Object.values(tasks),
  };
  return props;
};

На первый взгляд в коде всё нормально, но на самом деле Object.values создаёт каждый раз новый объект, даже если tasks остались прежними. А значит ни о какой эффективности не может быть и речи. Очевидным решением будет перенести эту логику внутрь компонента, но тогда теряется одно из главных преимуществ маппинга. Компоненты завязываются на структуру состояния и выполняют работу по подготовке данных, которая, кстати, начнёт дублироваться.

Для решения этой задачи создан пакет reselect. Он позволяет создавать специальные функции "селекторы", которые выполняют мемоизацию результата. То есть если данные не поменялись, то и результат работы функции будет тем же самым значением или объектом в случае составных данных.

Отмечу, что reselect не связан ни с Redux, ни с React. Нет никакого слоя интеграции. Селекторы сами по себе и их легко использовать в контейнерах без конфигурации.

import { createSelector } from 'reselect';

// Обычная функция извлекающая нужный срез данных из состояния
const getTasks = (state) => state.tasks;

// селектор на основе функции
const publishedTasksSelector = createSelector(
  getTasks,
  (tasks) => {
    console.log('selector');
    return tasks.filter((t) => t.state === 'published');
  },
);

const state = {
  tasks: [
    { name: 'buy milk', state: 'archived' },
    { name: 'rise money', state: 'published' },
  ],
};

console.log(publishedTasksSelector(state));
// => selector
// => [{ name: 'rise money', state: 'published' }]

// Повторный вызов не производит вычислений
console.log(publishedTasksSelector(state));
// => [{ name: 'rise money', state: 'published' }]

https://repl.it/@hexlet/js-redux-reselect-composition

Перед тем как создать первый селектор, нужно написать функцию, которая принимает на вход состояние и возвращает нужный срез данных. В нашем случае используется функция getTasks. Затем с помощью функции createSelector создаётся селектор. В примере выше в функцию createSelector передаётся наша исходная функция и вторая функция, которая производит фильтрацию данных, полученных первой функцией.

Посмотрите на этот код:

const getTasks = (state) => Object.values(state.tasks);

const mapStateToProps = (state) => {
  const props = {
    tasks: getTasks(state),
  };
  return props;
};

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

import { createSelector } from 'reselect';

const getTasks = (state) => state.tasks;
const tasksSelector = createSelector(
  getTasks,
  (tasks) => Object.values(tasks),
);

const mapStateToProps = (state) => {
  const props = {
    tasks: tasksSelector(state),
  };
  return props;
};

Хорошая новость в том, что селекторы можно соединять:

import { createSelector } from 'reselect';

const getTasks = (state) => state.tasks;
const tasksSelector = createSelector(
  getTasks,
  (tasks) => Object.values(tasks),
);
const publishedTasksSelector = createSelector(
  tasksSelector,
  (tasks) => tasks.filter((t) => t.state === 'published'),
);
const percentOfFinishedTasksSelector = createSelector(
  tasksSelector,
  publishedTasksSelector,
  (tasks, publishedTasks) => (publishedTasks.length / tasks.length) * 100,
)

const mapStateToProps = (state) => {
  const props = {
    tasks: tasksSelector(state),
    publishedTasks: publishedTasksSelector(state),
    percentOfFinishedTasks: percentOfFinishedTasksSelector(state),
  };
  return props;
};

Как это работает:

  • Селектор вызывает все переданные ему селекторы (которые в свою очередь делают то же самое и так до самого конца вложенности) и собирает результаты их вызовов в массив results.
  • Селектор вызывает последнюю переданную функцию как f(...results). Другими словами, количество аргументов в последней переданной функции селектору равно количеству селекторов, переданных перед этой функцией.
  • То, что получилось, и есть результат, который вернёт селектор (а заодно сохранит внутри).

Хотя по началу такая комбинаторика может пугать, в реальности селекторы очень простая вещь. Кроме мемоизации, они позволяют переиспользовать выборки в разных компонентах. В файловой системе рекомендуется размещать их по пути selectors/index.js.

Когда стоит использовать селекторы, а когда нет? Большинству приложений они не понадобятся. Фронтенд-приложения редко оперируют большим количеством данных одновременно. Разнообразные списки или формы хранят в себе максимум сотни или тысячу элементов. Более того, оптимизировать код имеет смысл только тогда, когда приложение уже начало тормозить, и мы точно убедились, что проблема в пересчёте внутри mapStateToProps. До этого момента лучше про оптимизацию и селекторы не вспоминать.


<span class="translation_missing" title="translation missing: ru.web.courses.lessons.mentors.mentor_avatars">Mentor Avatars</span>

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

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

Для полного доступа к курсу, нужна профессиональная подписка

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

Получить доступ
115
курсов
892
упражнения
2241
час теории
3196
тестов

Зарегистрироваться

или войти в аккаунт

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

  • 115 курсов, 2000+ часов теории
  • 800 практических заданий в браузере
  • 250 000 студентов

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг».

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

Логотип компании Альфа Банк
Логотип компании Rambler
Логотип компании Bookmate
Логотип компании Botmother

Есть вопрос или хотите участвовать в обсуждении?

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

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг».