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

Как Recancellation оптимизирует перерисовку?

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

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

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

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

Reconciliation: как React оптимизирует перерисовку

Что такое Reconciliation

Reconciliation (примирение, согласование) - это алгоритм в React, который определяет, какие части DOM нужно обновить при изменении состояния. Это ключевой механизм оптимизации производительности.

Без reconciliation React пересчитывал бы весь DOM при каждом изменении состояния, что было бы крайне неэффективно.

Проблема без Reconciliation

// Без оптимизации - каждое изменение state перерисовывает весь элемент
function Counter() {
  const [count, setCount] = useState(0);
  
  // Каждый клик пересчитает ВЕСЬ список!
  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>+</button>
      <p>{count}</p>
      <ExpensiveList items={Array(1000).fill(0)} />
    </div>
  );
}

Результат: приложение тормозит при каждом обновлении.

Как работает Reconciliation

1. Виртуальный DOM

React создаёт два виртуальных дерева и сравнивает их:

// Текущее состояние
const vdom1 = {
  type: 'div',
  props: { className: 'counter' },
  children: [
    { type: 'button', props: {}, children: ['+'] },
    { type: 'p', props: {}, children: ['5'] }
  ]
};

// После setState
const vdom2 = {
  type: 'div',
  props: { className: 'counter' },
  children: [
    { type: 'button', props: {}, children: ['+'] },
    { type: 'p', props: {}, children: ['6'] }
  ]
};

2. Diffing Algorithm

React сравнивает деревья и находит различия:

// Изменения (patches)
const patches = [
  {
    type: 'update_text',
    path: [1],
    from: '5',
    to: '6'
  }
];

3. Обновление только измененных узлов

// React обновляет ТОЛЬКО <p>, не трогает <button>
domNode.children[1].textContent = '6';

Ключ (key) - основа Reconciliation

key помогает React определить, какой элемент какой:

// Плохо: без key или с индексом
function ItemList({ items }) {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{item.name}</li>
      ))}
    </ul>
  );
}

// Проблема: если удалить первый элемент, React перепутает остальные
// Хорошо: уникальный идентификатор
function ItemList({ items }) {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

// React правильно отследит добавление/удаление

Оптимизация с React.memo

// Компонент перерисовывается при каждом изменении родителя
function UserCard({ user }) {
  console.log('Render UserCard');
  return <div>{user.name}</div>;
}

// Оптимизация: перерисовать только если изменился user
const MemoizedUserCard = React.memo(UserCard);

function App() {
  const [count, setCount] = useState(0);
  const user = { name: 'Alice' };
  
  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
      <MemoizedUserCard user={user} />
    </div>
  );
}

useCallback для стабильности функций

// Плохо: новая функция при каждом рендере
function Parent() {
  const [count, setCount] = useState(0);
  
  const handleClick = () => console.log('clicked');
  
  return (
    <>
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
      <Child onClick={handleClick} />
    </>
  );
}

// Хорошо: стабильная функция
function Parent() {
  const [count, setCount] = useState(0);
  
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);
  
  return (
    <>
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
      <Child onClick={handleClick} />
    </>
  );
}

Fiber Architecture (React 16+)

Modern React использует Fiber для еще более тонкой оптимизации:

function SearchInput() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  
  const handleChange = (e) => {
    // Высокий приоритет: сразу обновить input
    setQuery(e.target.value);
    
    // Низкий приоритет: искать с задержкой
    startTransition(() => {
      fetchResults(e.target.value).then(setResults);
    });
  };
  
  return (
    <>
      <input value={query} onChange={handleChange} />
      <ResultsList results={results} />
    </>
  );
}

Практические советы

1. Используй правильные keys

// БЕЗ key - O(n^2) сложность
// С уникальным key - O(n) сложность
const list = items.map(item => <Item key={item.id} {...item} />);

2. Разделяй состояние

// Плохо: одно большое состояние
const [state, setState] = useState({ user, posts, comments });

// Хорошо: разные состояния
const [user, setUser] = useState();
const [posts, setPosts] = useState([]);
const [comments, setComments] = useState([]);

3. Выносите дорогие вычисления

const sortedList = useMemo(
  () => [...list].sort((a, b) => a.name.localeCompare(b.name)),
  [list]
);

Вывод

Reconciliation оптимизирует перерисовку через:

  • Сравнение виртуального дерева (diffing)
  • Обновление только измененных узлов DOM
  • Использование key для правильного отслеживания
  • React.memo для предотвращения ненужных перерисовок
  • useCallback/useMemo для стабильности
  • Fiber architecture для приоритизации обновлений
Как Recancellation оптимизирует перерисовку? | PrepBro