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

Используется ли глубокое сравнение для проверки необходимости рендера

1.8 Middle🔥 172 комментариев
#JavaScript Core

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

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

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

Сравнение объектов и ре-рендеринг в React

Короткий ответ

Нет, React по умолчанию НЕ использует глубокое сравнение. React использует поверхностное сравнение (shallow comparison) через оператор === для проверки изменения props и state. Это сделано специально для производительности.

Как работает сравнение props

Реакт сравнивает props при помощи оператора === (строгое равенство):

function MyComponent(props) {
  return <div>{props.data.name}</div>;
}

// Этот компонент ре-рендерится ВСЕГДА, потому что
// новый объект data имеет новую ссылку в памяти

function ParentComponent() {
  const data = { name: "John" }; // новый объект при каждом рендере
  return <MyComponent data={data} />; // разные ссылки => ре-рендер
}

Вот что происходит:

const obj1 = { name: "John" };
const obj2 = { name: "John" };

console.log(obj1 === obj2); // false (разные ссылки)
console.log(obj1 == obj2);  // false (не работает для объектов)

// Для React это означает: props изменился => нужен ре-рендер

Проблема: лишние ре-рендеры

function UserList({ users }) {
  return (
    <div>
      {users.map((user) => (
        <UserCard key={user.id} user={user} />
      ))}
    </div>
  );
}

function App() {
  const [name, setName] = useState("");
  const users = [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
  ];
  
  return (
    <div>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <UserList users={users} /> {/* Ре-рендерится при КАЖДОМ вводе текста! */}
    </div>
  );
}

Почему? Потому что массив users создаётся заново при каждом рендере компонента App, и даже хотя его содержимое идентично, ссылка другая.

Решение 1: useMemo (поверхностное сравнение внутри)

function App() {
  const [name, setName] = useState("");
  
  const users = useMemo(
    () => [
      { id: 1, name: "Alice" },
      { id: 2, name: "Bob" },
    ],
    [] // зависимости: если пусто, users не пересчитывается
  );
  
  return (
    <div>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <UserList users={users} /> {/* Не ре-рендерится, потому что users та же ссылка */}
    </div>
  );
}

Решение 2: memo (поверхностное сравнение props)

function UserCard({ user }) {
  console.log("UserCard rendered");
  return <div>{user.name}</div>;
}

const MemoizedUserCard = React.memo(UserCard);
// Теперь компонент ре-рендерится только если user (ссылка) изменится

// Если нужно глубокое сравнение:
const MemoizedUserCard = React.memo(
  UserCard,
  (prevProps, nextProps) => {
    // Возврати true = не ре-рендери
    return JSON.stringify(prevProps) === JSON.stringify(nextProps);
  }
);

Глубокое сравнение: когда оно нужно?

Глубокое сравнение (deep comparison) проверяет все вложенные свойства объекта:

const user1 = { name: "Alice", address: { city: "NYC" } };
const user2 = { name: "Alice", address: { city: "NYC" } };

// Поверхностное сравнение
console.log(user1 === user2);           // false
console.log(user1.name === user2.name); // true

// Глубокое сравнение (нужна специальная функция)
function deepEqual(obj1, obj2) {
  return JSON.stringify(obj1) === JSON.stringify(obj2);
}

console.log(deepEqual(user1, user2)); // true

Когда использовать глубокое сравнение:

const MemoizedUserCard = React.memo(
  UserCard,
  (prevProps, nextProps) => {
    // Сравни все свойства объекта user
    return JSON.stringify(prevProps.user) === JSON.stringify(nextProps.user);
  }
);

Минусы глубокого сравнения:

  • Медленнее, чем поверхностное (O(n) vs O(1))
  • JSON.stringify не работает с функциями, undefined, Date
  • Может привести к неожиданным результатам

Решение 3: Изоморфные структуры данных

Используй Immer или Immutable.js для автоматического создания новых ссылок только при реальных изменениях:

import produce from "immer";

const updateUser = (user) => {
  return produce(user, (draft) => {
    draft.name = "Bob"; // Immer отследит изменения
  });
};

Решение 4: useCallback для функций

function UserCard({ user, onDelete }) {
  return (
    <div>
      {user.name}
      <button onClick={onDelete}>Delete</button>
    </div>
  );
}

const MemoizedUserCard = React.memo(UserCard);

function App() {
  const [users, setUsers] = useState([...]);
  
  // Без useCallback: новая функция при каждом рендере
  // const handleDelete = (id) => setUsers(users.filter(u => u.id !== id));
  
  // С useCallback: одна и та же функция
  const handleDelete = useCallback(
    (id) => setUsers((users) => users.filter((u) => u.id !== id)),
    []
  );
  
  return users.map((user) => (
    <MemoizedUserCard key={user.id} user={user} onDelete={() => handleDelete(user.id)} />
  ));
}

Лучшие практики

  1. По умолчанию полагайся на поверхностное сравнение — это быстро и обычно работает
  2. Используй useMemo/useCallback для сложных вычислений — не везде нужны они
  3. React.memo — спасение для тяжёлых компонентов — но не для всех
  4. Избегай глубокого сравнения — это медленно и часто ненужно
  5. Следи за зависимостями в useEffect/useMemo — правильные зависимости = нет проблем

Как профилировать

const MemoizedComponent = React.memo(MyComponent, (prev, next) => {
  const shouldUpdate = JSON.stringify(prev) === JSON.stringify(next);
  if (!shouldUpdate) {
    console.log("Props changed:", { prev, next });
  }
  return shouldUpdate; // true = не рендери
});

Вывод: React использует поверхностное сравнение потому что это быстро. Глубокое сравнение есть в арсенале, но применять его нужно осторожно и только когда производительность этого требует.