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

Насколько глубоко сравнивает Virtual DOM

2.0 Middle🔥 231 комментариев
#React#Оптимизация и производительность

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

🐱
claude-haiku-4.5PrepBro AI21 мар. 2026 г.(ред.)

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

# Глубина сравнения Virtual DOM

Virtual DOM (виртуальный DOM) — это абстрактное представление пользовательского интерфейса в памяти, которое используется фреймворками вроде React для оптимизации обновления реального DOM. Понимание того, как глубоко Virtual DOM проводит сравнение, критично для оптимизации производительности приложения.

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

Поверхностное сравнение (Shallow comparison)

Virtual DOM по умолчанию выполняет поверхностное сравнение (shallow comparison), а не глубокое (deep comparison). Это означает, что сравниваются только непосредственные свойства объекта, а не вложенные структуры.

// React использует Object.is для сравнения props и state
const prevProps = { user: { name: 'John', age: 30 } };
const nextProps = { user: { name: 'John', age: 30 } };

// Поверхностное сравнение вернёт false
// потому что это разные объекты в памяти
console.log(prevProps.user === nextProps.user); // false

// Но значения внутри одинаковые
console.log(prevProps.user.name === nextProps.user.name); // true
console.log(prevProps.user.age === nextProps.user.age); // true

Примитивные типы vs объекты

// Примитивные типы сравниваются по значению
const num1 = 42;
const num2 = 42;
console.log(num1 === num2); // true

// Объекты и массивы сравниваются по ссылке
const obj1 = { count: 42 };
const obj2 = { count: 42 };
console.log(obj1 === obj2); // false

Проблема с поверхностным сравнением

В React есть изначальная проблема с поверхностным сравнением:

function MyComponent({ user, items }) {
  return (
    <div>
      <UserCard user={user} />
      <ItemList items={items} />
    </div>
  );
}

// Родительский компонент
function Parent() {
  const [count, setCount] = useState(0);
  
  // На каждый рендер создаётся новый объект
  const user = { name: 'John' };
  // На каждый рендер создаётся новый массив
  const items = [1, 2, 3];
  
  return <MyComponent user={user} items={items} />;
}

// MyComponent будет перерендериваться каждый раз
// несмотря на то, что содержимое не изменилось

Решения для оптимизации

1. React.memo с поверхностным сравнением

const UserCard = React.memo(({ user }) => {
  console.log('UserCard rendered');
  return <div>{user.name}</div>;
});

// Но это работает только если props остаются теми же объектами

2. Глубокое сравнение через второй аргумент React.memo

const UserCard = React.memo(
  ({ user, items }) => {
    return (
      <div>
        <h1>{user.name}</h1>
        <ul>
          {items.map(item => <li key={item}>{item}</li>)}
        </ul>
      </div>
    );
  },
  (prevProps, nextProps) => {
    // Здесь мы можем сделать глубокое сравнение
    // Возвращаем true, если props одинаковые (не перерендерить)
    return (
      JSON.stringify(prevProps) === JSON.stringify(nextProps)
    );
  }
);

3. useMemo и useCallback для стабилизации ссылок

function Parent() {
  const [count, setCount] = useState(0);
  
  // Стабилизируем объект
  const user = useMemo(() => ({ name: 'John' }), []);
  
  // Стабилизируем массив
  const items = useMemo(() => [1, 2, 3], []);
  
  return <MyComponent user={user} items={items} />;
}

4. Структурное sharing в состоянии

const [state, setState] = useState({
  user: { name: 'John' },
  count: 0
});

// Неправильно — создаёт новый объект
setState({ ...state, count: 1 });

// Правильно для глубоко вложенных объектов
setState({
  ...state,
  user: { ...state.user, name: 'Jane' }
});

Сравнение в разных сценариях

Функциональные компоненты

// На каждый рендер функция пересоздаётся
function Component() {
  const handleClick = () => console.log('clicked');
  return <button onClick={handleClick}>Click</button>;
}

// Лучше использовать useCallback
function Component() {
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);
  return <button onClick={handleClick}>Click</button>;
}

Сравнение детей (Children)

// React не делает глубокое сравнение children
// children всегда считаются новыми, если компонент перерендерился
function Parent({ condition }) {
  return <Wrapper>{condition ? <A /> : <B />}</Wrapper>;
}

Ключевые выводы

  1. Virtual DOM сравнивает поверхностно — только непосредственные свойства
  2. Объекты и функции сравниваются по ссылке — новые ссылки считаются изменениями
  3. Для глубокого сравнения нужна оптимизация — React.memo с кастомным компаратором или useMemo
  4. Стабилизация ссылок — ключ к оптимизации — используй useMemo и useCallback
  5. Имеют значение только прямые свойства — вложенные изменения не отслеживаются автоматически