Будет ли работать React.memo если пропсы передаются в виде объекта, который меняется по ссылке
Что проверяют
Понимаете ли вы, как React.memo сравнивает пропсы, и видите ли два разных сценария за формулировкой «меняется по ссылке». Следом обычно спрашивают про функции в пропсах и про то, зачем тогда useMemo и useCallback.
Как отвечать
React.memo пропускает повторный рендер, если все пропсы поверхностно равны прежним. Каждый проп сравнивается через Object.is, то есть объекты по ссылке, а не по содержимому. Отсюда два случая:
- новый объект на каждый рендер:
<User user={{ name }} />или объект, собранный в теле родителя. Ссылка каждый раз новая, сравнение проваливается, и memo не срабатывает никогда, а время на сравнение всё равно тратится; - тот же объект, изменённый мутацией:
user.name = 'Bob'и ререндер родителя. Ссылка прежняя, memo решает, что ничего не поменялось, и показывает устаревшие данные. Это уже не лишний рендер, а баг.
Как чинить:
- стабилизировать ссылку:
useMemo(() => ({ name }), [name]), новый объект появится только при сменеname; - передавать примитивы:
name={user.name}вместо целого объекта; - свой компаратор: второй аргумент
React.memo(Component, (prev, next) => ...)возвращаетtrue, если рендер не нужен. Легко забыть поле и получить устаревший экран; - обновлять иммутабельно:
setUser({ ...user, name }), чтобы изменение данных всегда меняло ссылку.
Стоит упомянуть React Compiler: он сам расставляет мемоизацию на этапе сборки, и ручных useMemo в проектах с ним заметно меньше.
Где спотыкаются
Советуют спред или Object.assign, «чтобы ссылка не менялась». Оба создают новый объект, то есть новую ссылку.
Забывают про функции и children. Стрелочная функция в пропсах или JSX, переданный как children, ломает memo так же, как новый объект.
Ждут, что memo защитит от всех рендеров. Изменение собственного состояния компонента или контекста, который он читает через useContext, всё равно вызовет рендер.