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

Как происходит отрисовка элементов на странице в React?

1.7 Middle🔥 143 комментариев
#React#Архитектура и паттерны

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

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

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

Процесс отрисовки элементов на странице в React

Отрисовка (rendering) - это процесс преобразования компонентов в DOM элементы, видимые пользователю. React использует Virtual DOM для оптимизации этого процесса.

Этапы отрисовки

1.렌더Rendering фазе

// При изменении state/props React запускает render
function Counter() {
  const [count, setCount] = useState(0);
  
  // Функция компонента вызывается - это render
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

// React.createElement вывов:
React.createElement('div', null,
  React.createElement('p', null, 'Count: ', count),
  React.createElement('button', {...}, 'Increment')
);

2. Reconciliation (сравнение)

// Virtual DOM - это объект, не настоящий DOM
const vdom = {
  type: 'div',
  props: { className: 'container' },
  children: [
    { type: 'p', props: {}, children: ['Hello'] },
    { type: 'button', props: { onClick: handleClick }, children: ['Click'] }
  ]
};

// React сравнивает старый и новый vdom
function reconcile(oldVdom, newVdom) {
  // Если тип изменился - пересоздать
  if (oldVdom?.type !== newVdom.type) {
    return 'replace';
  }
  
  // Если текст изменился - обновить
  if (oldVdom?.children !== newVdom.children) {
    return 'update';
  }
  
  return 'same';
}

3. Commit фаза (реальное обновление DOM)

// После reconciliation React обновляет реальный DOM
// 1. Обновляет элементы
document.querySelector('p').textContent = 'New text';

// 2. Запускает effects
useEffect(() => {
  console.log('Component mounted or updated');
}, [dependency]);

// 3. Запускает cleanup функции
useEffect(() => {
  return () => {
    console.log('Cleanup');
  };
}, []);

Полный цикл жизни компонента

function LifecycleExample() {
  const [count, setCount] = useState(0);
  
  // 1. RENDER фаза - функция компонента вызывается
  console.log('Render');
  
  // 2. При монтировании
  useEffect(() => {
    console.log('Mount');
    
    // 3. COMMIT фаза - DOM обновлен
    
    // 4. При размонтировании
    return () => {
      console.log('Unmount');
    };
  }, []); // зависимости пусты - один раз
  
  // При изменении count
  useEffect(() => {
    console.log('Count changed to', count);
  }, [count]);
  
  return (
    <button onClick={() => setCount(count + 1)}>
      {count}
    </button>
  );
}

// Порядок при клике:
// 1. onClick вызывается
// 2. setCount обновляет state
// 3. Render фаза - функция вызывается заново
// 4. Reconciliation - Virtual DOM сравнивается
// 5. Commit фаза - DOM обновляется
// 6. useEffect с [count] зависимостью запускается

React Fiber - детальный процесс

// React разбивает работу на куски (fibers)
function renderWithFiber(element) {
  // 1. Создать fiber дерево
  let fiber = {
    type: element.type,
    props: element.props,
    dom: null,
    parent: null,
    child: null,
    sibling: null
  };
  
  // 2. Пройти по всему дереву
  reconcileChildren(fiber, element.props.children);
  
  // 3. Создать/обновить DOM в commit фазе
  commitRoot();
}

// Это позволяет React:
// - Делить работу на части
// - Прерывать и продолжать рендер
// - Приоритизировать обновления

Оптимизация отрисовки

Избегай ненужных re-renders

// ПЛОХО - Parent перерисовывается часто
function Parent() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <Child data={count} /> {/* Child перерисуется */}
      <button onClick={() => setCount(count + 1)}>
        Click
      </button>
    </div>
  );
}

// ХОРОШО - memo предотвращает ненужный render
const Child = React.memo(({ data }) => {
  return <div>{data}</div>;
});

// ХОРОШО - разделить state
function Parent() {
  return (
    <div>
      <ChildWithOwnState />
      <Counter />
    </div>
  );
}

useMemo и useCallback

function ExpensiveComponent({ items, onFilter }) {
  // Кэшируем результат, если items не изменились
  const filteredItems = useMemo(() => {
    return items.filter(item => item.active);
  }, [items]);
  
  // Кэшируем функцию, чтобы Child не перерисовывалась
  const handleClick = useCallback((id) => {
    onFilter(id);
  }, [onFilter]);
  
  return (
    <div>
      {filteredItems.map(item => (
        <Item key={item.id} onClick={() => handleClick(item.id)} />
      ))}
    </div>
  );
}

Порядок выполнения hooks

function HookOrder() {
  // 1. State инициализируется
  const [count, setCount] = useState(0);
  
  // 2. Effects выполняются ПОСЛЕ render
  useEffect(() => {
    console.log('After render:', count);
    
    // Cleanup выполнится перед следующим render
    return () => {
      console.log('Before next render or unmount');
    };
  }, [count]);
  
  // 3. Компонент возвращает JSX
  return <div>{count}</div>;
}

// При монтировании:
// State инициализируется -> JSX возвращается -> Effect выполняется

// При update:
// Cleanup выполняется -> State обновляется -> JSX возвращается -> Effect

Батчинг (Batching)

function BatchingExample() {
  const [a, setA] = useState(0);
  const [b, setB] = useState(0);
  
  const handleClick = () => {
    // React 18 батчит эти вызовы
    // Только один render и один commit!
    setA(a + 1);
    setB(b + 1);
  };
  
  // Даже в async коде в React 18
  const handleAsync = async () => {
    await fetchSomething();
    // Батчится автоматически!
    setA(a + 1);
    setB(b + 1);
  };
  
  return <button onClick={handleClick}>Click</button>;
}

Отрисовка в React - это не просто обновление DOM, это сложный процесс оптимизации, обхода, и управления побочными эффектами. Профессиональный разработчик должен понимать каждый этап.