Как React сравнивает значения?
Основы React
Во всех местах, где React решает «изменилось или нет», используется одно правило — поверхностное сравнение через Object.is (или поэлементное Object.is для наборов значений). Глубоких сравнений React не делает никогда.
Object.is — базовый примитив
Как строгое равенство ===, но Object.is(NaN, NaN) === true и Object.is(+0, -0) === false. Главное:
- примитивы сравниваются по значению;
- объекты, массивы, функции — по ссылке: два одинаковых по содержимому объекта не равны.
Где и что сравнивается
- Сеттер useState / возврат редьюсера: новое значение против старого — равно → рендер пропущен (bail out). Поэтому мутация + сеттер с той же ссылкой «не работает».
- Deps хуков (useEffect/useMemo/useCallback): поэлементно по позициям; один отличающийся элемент — перезапуск.
- React.memo / PureComponent: shallow equal — каждый проп (поле state) через Object.is.
- Context: старое value против нового — изменилось → все потребители ререндерятся.
- useSyncExternalStore: снапшоты стора.
- Дифф DOM-props в reconciliation: атрибуты узла поэлементно — в патч идут отличия.
Практические следствия
setUser({ ...user }); // «изменилось» (новая ссылка) — рендер будет
user.name = 'X'; setUser(user); // «не изменилось» (та же ссылка) — рендера нет- Иммутабельность обязательна: изменение = новая ссылка.
- Инлайн-объекты/функции = «всегда новое» → ломают memo и deps; нужны useMemo/useCallback.
- Глубокие изменения без новой ссылки невидимы для всех механизмов.
Коротко для собеседования
Везде одно правило: Object.is поверхностно — примитивы по значению, объекты/функции по ссылке; глубокого сравнения нет. Применяется в сеттерах state (bail out), deps хуков, React.memo, контексте и диффе props. Отсюда два столпа React-кода: иммутабельные обновления и стабильные ссылки.