PassFront
  1. PassFront
  2. База знаний
  3. Хуки

Как React Hooks сравнивают deps между собой?

Хуки

Хуки (useEffect, useMemo, useCallback) сравнивают deps поэлементно и поверхностно: каждый элемент нового массива сопоставляется с элементом той же позиции из прошлого рендера через Object.is.

Алгоритм

// упрощённо — то, что делает React:
function areDepsEqual(next, prev) {
  if (prev === null) return false;          // первый рендер — «изменилось»
  for (let i = 0; i < next.length; i++) {
    if (!Object.is(next[i], prev[i])) return false;
  }
  return true;
}
  • Массивы сравниваются по позициям (поэтому длина и порядок deps должны быть стабильны — литерал в месте вызова).
  • Хоть один элемент отличается — эффект перезапускается / значение пересчитывается / функция пересоздаётся.

Что такое Object.is

Почти строгое равенство ===, с двумя отличиями: Object.is(NaN, NaN) === true и Object.is(0, -0) === false. Главное:

  • примитивы — по значению: 'a' === 'a', 5 === 5;
  • объекты/массивы/функции — по ссылке: {} !== {} даже при одинаковом содержимом.

Следствия

const options = { page };                       // новая ссылка каждый рендер
useEffect(() => load(options), [options]);      // «изменилось» всегда!

const user = useMemo(() => ({ id }), [id]);     // стабильная ссылка
useEffect(() => track(user), [user]);           // перезапуск только при смене id
  • Глубокого сравнения нет никогда (дорого); мутации не видны (ссылка та же).
  • Нестабильные объекты/функции в deps → бесконечные перезапуски; лечится useMemo/useCallback, примитивами или переносом в эффект.

Коротко для собеседования

Deps сравниваются поэлементно через Object.is (по сути ===, но NaN равен NaN): примитивы по значению, объекты/функции по ссылке; глубокого сравнения нет. Один отличающийся элемент — хук «перезапускается». Отсюда требования: стабильные ссылки в deps и постоянный состав массива.

Другие вопросы раздела