Нужно ли мемоизировать данные, полученные из Redux селектора в компоненте
Что проверяют
Понимаете ли вы, по какому признаку useSelector решает перерисовать компонент, и где мемоизация действительно нужна, а где она карго-культ.
Как отвечать
useSelector после каждого действия вызывает селектор и сравнивает результат с предыдущим по ссылке. Пока селектор возвращает примитив или уже существующий кусок состояния, мемоизировать нечего. Проблема появляется, когда селектор создаёт новое значение на каждом вызове: filter, map, объектный литерал из нескольких полей. Ссылка каждый раз новая, и компонент перерисовывается на любое действие в приложении, даже никак с ним не связанное.
Порядок решений такой:
- вернуть примитив или существующую ссылку, при необходимости взять поля отдельными вызовами;
- завести мемоизированный селектор через
createSelector, если значение производное и его надо считать; - передать вторым аргументом поверхностное сравнение, когда нужен объект из нескольких полей сразу.
const selectActive = createSelector(
[selectItems],
(items) => items.filter((item) => item.active)
);Отдельно стоит сказать про параметр: такой селектор по умолчанию помнит один последний набор аргументов. Один общий селектор с идентификатором, вызванный из десяти карточек списка, промахивается мимо кэша каждый раз, и лечится это фабрикой селектора на экземпляр компонента.
Где спотыкаются
Оборачивают результат селектора в useMemo внутри компонента. К моменту, когда useMemo что-то посчитает, лишний рендер уже случился: новую ссылку вернул селектор, и остановить перерисовку можно только в нём.
Подписываются на всё дерево состояния, возвращая его целиком, а нужное поле достают потом в теле компонента. Такой компонент рендерится на каждое действие, и никакая мемоизация внутри это не чинит.
Мемоизируют не глядя на замеры. Пересчёт дешёвого селектора стоит меньше, чем хранение кэша и сравнение аргументов, а профилировщик обычно показывает, что виноват вовсе не селектор.