Какие триггеры вызывают перерисовку React компонента
Что проверяют
Понимаете ли вы, когда React заново вызывает функцию компонента, и отличаете ли рендер от изменения DOM. Следом обычно спрашивают, почему React.memo не помог и как найти лишние рендеры.
Как отвечать
Причин повторного рендера немного:
- своё состояние: сеттер
useStateилиdispatchизuseReducer. Если новое значение равно старому поObject.is, React пропускает обновление. Поэтомуpushв массив из состояния рендер не вызовет: ссылка та же; - рендер родителя: дочерние компоненты рендерятся вслед за родителем всегда, даже если их пропсы не изменились. Остановить это может
React.memo(илиPureComponentу классов), который поверхностно сравнивает пропсы; - контекст: смена
valueу провайдера рендерит всех, кто читает контекст черезuseContext, иmemoтут не защищает; - внешний стор: Redux, Zustand и подобные подписываются через
useSyncExternalStore, рендер случается, когда меняется выбранный срез; - смена
key: это уже не рендер, а размонтирование и новый монтаж с потерей состояния; - принудительно:
forceUpdate()в классовых компонентах, сейчас встречается редко.
Дальше покажите, что рендер ещё не значит работу с DOM: React сравнивает новое дерево со старым и в фазе коммита меняет только отличия. Лишний рендер дорог, когда компонент тяжёлый или их тысячи. Искать такие места удобно через Profiler в React DevTools. Лечат переносом состояния ниже по дереву, передачей через children, memo вместе с useMemo и useCallback. С включённым React Compiler большую часть мемоизации он расставляет сам.
Где спотыкаются
Говорят, что дочерний компонент рендерится, только когда меняются его пропсы. Без memo это неверно: достаточно рендера родителя.
Оборачивают компонент в memo, но передают ему новый объект или стрелочную функцию на каждом рендере. Сравнение пропсов всегда проваливается.
Пугаются двойного рендера в разработке и ищут баг. Это StrictMode намеренно вызывает компоненты дважды, в продакшен-сборке этого нет.