← Назад к вопросам

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

2.0 Middle🔥 231 комментариев
#React

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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

Механизм сравнения dependencies

React использует Object.is() для сравнения каждой зависимости в массиве dependencies. Это отличается от обычного оператора ===, хотя в большинстве случаев результат одинаков.

Object.is() - алгоритм сравнения

// Object.is() сравнивает значения
Object.is(5, 5) // true
Object.is("hello", "hello") // true
Object.is(true, true) // true
Object.is(NaN, NaN) // true - отличие от ===!
Object.is(+0, -0) // false - отличие от ===!

// Для объектов - сравнение по ссылке (как ===)
const obj1 = { name: "Алиса" };
const obj2 = { name: "Алиса" };
Object.is(obj1, obj2) // false - разные ссылки
Object.is(obj1, obj1) // true - одна ссылка

Как это работает в useEffect

function Component() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState("");
  
  useEffect(() => {
    console.log("Effect запустился");
    // Что-то делаем
  }, [count, text]); // Dependency array
  
  // При каждом рендере React:
  // 1. Сохраняет старый deps: [0, ""]
  // 2. Вычисляет новый deps: [count, text]
  // 3. Сравнивает каждый элемент с Object.is()
  // 4. Если хоть один отличается - запускает эффект
}

Поэтапное сравнение

// Шаг 1: Первый рендер - эффект всегда запускается
useEffect(() => {
  console.log("Инициализация");
}, []);

// Шаг 2: Второй рендер
const oldDeps = [0];
const newDeps = [0];
Object.is(oldDeps[0], newDeps[0]) // true - эффект НЕ запустится

// Шаг 3: Третий рендер (count изменился)
const oldDeps = [0];
const newDeps = [1];
Object.is(oldDeps[0], newDeps[0]) // false - эффект запустится

Проблема с объектами в dependencies

Поскольку объекты сравниваются по ссылке, создание нового объекта в каждом рендере приведёт к повторному запуску эффекта:

function UserProfile() {
  // ПРОБЛЕМА: новый объект при каждом рендере
  const user = { id: 1, name: "Алиса" };
  
  useEffect(() => {
    console.log("Загружаем данные для", user.name);
    // Запрос на сервер
  }, [user]); // user всегда новый - эффект запустится бесконечно!
}

// РЕШЕНИЕ 1: вынеси объект вне компонента
const user = { id: 1, name: "Алиса" };

function UserProfile() {
  useEffect(() => {
    console.log("Загружаем", user.name);
  }, [user]); // Одна и та же ссылка
}

// РЕШЕНИЕ 2: используй примитивные значения
function UserProfile({ userId }: { userId: number }) {
  useEffect(() => {
    console.log("Загружаем юзера", userId);
  }, [userId]); // Сравнивается по значению
}

// РЕШЕНИЕ 3: используй useMemo
function UserProfile() {
  const user = useMemo(
    () => ({ id: 1, name: "Алиса" }),
    [] // Зависимости для самого useMemo
  );
  
  useEffect(() => {
    console.log("Загружаем", user.name);
  }, [user]); // Одна и та же ссылка (создана один раз)
}

Аналогично для useCallback и useMemo

function Component() {
  const [count, setCount] = useState(0);
  
  // Зависимости сравниваются так же
  const memoizedValue = useMemo(
    () => expensiveCalculation(count),
    [count] // Object.is сравнивает count
  );
  
  const memoizedCallback = useCallback(
    () => {
      console.log(count);
    },
    [count] // Object.is сравнивает count
  );
  
  return <button onClick={memoizedCallback}>{count}</button>;
}

Практическая таблица сравнения

ТипСравнениеПример
NumberПо значению1 === 1 -> true
StringПо значению"hello" === "hello" -> true
BooleanПо значениюtrue === true -> true
ObjectПо ссылке{} === {} -> false
ArrayПо ссылке[] === [] -> false
FunctionПо ссылке(() => {}) === (() => {}) -> false
NaNСпециальноObject.is(NaN, NaN) -> true

Типичная ошибка

// НЕПРАВИЛЬНО - бесконечный цикл
function Search({ query }) {
  const [results, setResults] = useState([]);
  
  useEffect(() => {
    setResults([{ title: "Результат" }]); // Новый массив
  }, [results]); // results меняется -> эффект запускается
  // -> setResults создаёт новый массив
  // -> эффект запускается снова...
}

// ПРАВИЛЬНО
function Search({ query }) {
  const [results, setResults] = useState([]);
  
  useEffect(() => {
    // Запрос на сервер
    fetch(`/api/search?q=${query}`)
      .then(r => r.json())
      .then(setResults);
  }, [query]); // Зависимость от query, не от results
}

Заключение

React сравнивает dependencies поэлементно с Object.is():

  • Примитивы сравниваются по значению
  • Объекты и массивы сравниваются по ссылке
  • Если хоть одна зависимость изменилась - эффект запускается
  • Используй useMemo для объектов и useCallback для функций, если они зависимости
  • Избегай создания новых объектов в каждом рендере в deps array
Как React Hooks сравнивают deps между собой? | PrepBro