логотип PurpleSchool
логотип PurpleSchool

Как работает рендеринг в React

Автор

Олег Марков

Введение

Рендеринг в React — это фундаментальное понятие, без которого невозможно представить работу этого популярного фреймворка. Рендеринг отвечает за то, как код на JavaScript превращается в красивые и интерактивные пользовательские интерфейсы в браузере. Механизм работы рендеринга кажется простым на первый взгляд: вы пишете компонент — он отображается. Но на самом деле за этим кроется мощная архитектура: работа с Virtual DOM, диффинг-алгоритмы, оптимизация перерасчёта и обновления, жизненный цикл компонентов. Давайте подробно разберём, как React справляется с рендерингом и что происходит «под капотом».

Что такое рендеринг в React

Рендеринг — это процесс превращения описания UI в реальное DOM-дерево, видимое пользователю. В React вы описываете интерфейс через компоненты на JavaScript или TypeScript, используя синтаксис JSX. Когда компонент рендерится, React создает дерево элементов, которое затем превращается в реальные DOM-элементы.

Есть два типа рендеринга:

  1. Первичный рендер (Initial Render) — когда компонент впервые появляется на странице.
  2. Повторный рендер (Re-render) — когда компонент обновляется из-за изменения его состояния (state) или свойств (props).

Важно понимать, что реакция на изменения данных — одна из самых сильных сторон React.

Рендеринг в React - это процесс преобразования React-компонентов в DOM-элементы, которые отображаются в браузере. Понимание того, как React выполняет рендеринг, имеет решающее значение для оптимизации производительности и создания отзывчивых приложений. Если вы хотите детальнее разобраться в механизмах рендеринга в React, узнать о виртуальном DOM и стратегиях оптимизации — приходите на наш большой курс Основы React, React Router и Redux Toolkit. На курсе 177 уроков и 17 упражнений, AI-тренажеры для безлимитной практики с кодом и задачами 24/7, решение задач с живым ревью наставника, еженедельные встречи с менторами.

Virtual DOM — основа быстрого рендеринга

Как работает Virtual DOM

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

  • Компонент возвращает JSX (например, <div>Hello</div>)
  • React превращает JSX в специальное описание JavaScript-объекта (элемент — “React element”)
  • На основе этого объекта строится дерево Virtual DOM

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

Почему Virtual DOM эффективен

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

Жизненный цикл рендеринга

React-компоненты проходят несколько этапов во время рендеринга. Вот главные из них:

  • Монтирование: компонент появляется в DOM впервые.
  • Обновление: компонент получает новые props или изменяется его state.
  • Размонтирование: компонент удаляется из DOM.

Каждый этап даёт вам возможность “вмешаться” в процесс с помощью хуков жизненного цикла (например, useEffect, componentDidMount и др.).

Когда и почему React инициирует рендеринг

React инициирует рендеринг, когда происходит одно из следующих событий:

  1. Изменилась функция рендера — компонент впервые вызывается (например, мы добавили его в приложение).
  2. Обновились свойства — компоненту передали новые значения через props.
  3. Обновилось состояние — вызван setState в классовом компоненте или useState в функциональном.
  4. Контекст — если компонент подписан на контекст (через useContext) и значение контекста поменялось.

Пример: когда срабатывает рендер

import React, { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  // Каждый раз, когда вызывается setCount — происходит рендер!
  return (
    <div>
      <p>Текущее значение: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Увеличить
      </button>
    </div>
  );
}
// При клике на кнопку: компонент рендерится заново

Как работает процесс повторного рендера

Когда состояние или пропсы компонента изменяются, React начинает новый рендер:

  1. Запускается функция рендера (компонент возвращает JSX).
  2. React строит новое дерево Virtual DOM.
  3. Сравнивает (diffing) новое дерево Virtual DOM со старым.
  4. Находит отличия (patches).
  5. Применяет только нужные изменения к реальному DOM.

Пример сравнения деревьев

Допустим, был код:

<div>
  <h1>Привет</h1>
  <p>Сообщение</p>
</div>

А потом мы изменяем <p>Сообщение</p> на <p>Новое сообщение</p>. React увидит различие только в содержимом <p> и изменит именно этот текст в DOM — остальные элементы трогаться не будут.

React Fiber — сердце современного рендеринга

С версии React 16 процесс рендеринга управляется новым движком — Fiber. Он позволил разбить рендеринг на мелкие части (unit of work), чтобы не блокировать основной поток браузера, делая обновления плавными, а приложения отзывчивыми даже на больших данных.

Кратко о Fiber:

  • Позволяет приоритетно обрабатывать задачи (например, анимации первичнее сложных расчётов)
  • Даёт гибкость для введения новых возможностей (таких как Concurrent Mode)
  • Более “гранулированный” контроль над рендерингом

Методы и хуки, влияющие на рендер компонента

Хук useState

useState — один из самых популярных хуков для хранения состояния. При вызове функции обновления состояния происходит рендер компонента.

const [value, setValue] = useState(0);

setValue(value + 1); // Компонент ререндерится

Хук useEffect

useEffect вызывается после рендера. Давайте посмотрим на пример:

useEffect(() => {
  // Этот код выполнится после каждого рендера, если не указан второй аргумент
  document.title = `Счётчик: ${count}`;
});

Если вы хотите, чтобы эффект сработал только при первом рендере:

useEffect(() => {
  // Аналог componentDidMount
}, []);

Хук useMemo и useCallback

Они помогают оптимизировать рендеринг, предотвращая лишние пересчеты функций и значений.

const expensiveValue = useMemo(() => {
  // Здесь какая-то тяжелая функция
  return complexCalculation(count);
}, [count]); // Пересчитывать только при изменении count

useCallback — то же, но для функций. Это избавляет дочерние компоненты от лишних рендеров при неизменности функций в пропсах.

Оптимизация рендеринга

Мемоизация компонентов — React.memo

Если ваш компонент зависит только от props и не должен рендериться при любом изменении родителя, используйте React.memo:

const MyComponent = React.memo(function MyComponent({ value }) {
  // Ререндер только если value изменился
  return <div>{value}</div>;
});

shouldComponentUpdate и PureComponent

Для классовых компонентов оптимизация ререндера достигается двумя инструментами:

  • shouldComponentUpdate(nextProps, nextState) — позволяет отменить рендер, если входные данные не изменились.
  • PureComponent — это компонент, который реализует поверхностное сравнение пропсов и состояния.
class MyComp extends React.PureComponent {
  render() {
    // Будет рендериться только если изменились props или state
    return <span>{this.props.value}</span>;
  }
}

Ключи при рендеринге списков

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

{items.map(item => (
  <li key={item.id}>{item.name}</li>
))}

Важно: Ключ должен быть уникальным и неизменным для элемента списка.

Контроль над рендерингом: форсированный и предотвращённый рендер

Форсированный рендер (forceUpdate)

В редких случаях можно принудительно инициировать рендер с помощью forceUpdate() (классовые компоненты). В функциональных компонентах этого нет, но обычно это и не требуется.

Пропуск рендера

Для предотвращения рендера используйте оптимизации (React.memo, shouldComponentUpdate) или не меняйте состояние без необходимости.

Как React рендерит дочерние компоненты

Когда родительский компонент обновляется, все его дочерние компоненты, по умолчанию, тоже проходят рендер. Чтобы рендерились только затронутые данные — используйте вышеописанные оптимизации.

function Parent({ value }) {
  return (
    <Child value={value} />
  );
}
// Child рендерится при каждом рендере Parent, если не используется React.memo

Практический пример: оптимизация рендеринга

Представьте, что у вас есть список задач и форма добавления новой задачи. Посмотрите, как оптимизировать рендеринг списка при добавлении задачи.

// Большой компонент списка задач
const TaskList = React.memo(({ tasks }) => {
  return tasks.map(task => (
    <TaskItem key={task.id} task={task} />
  ));
});

// Компонент добавления задачи
function AddTaskForm({ onAdd }) {
  const [value, setValue] = useState('');
  return (
    <form onSubmit={e => {
      e.preventDefault();
      onAdd(value);
      setValue('');
    }}>
      <input value={value} onChange={e => setValue(e.target.value)} />
      <button type="submit">Добавить</button>
    </form>
  );
}

function TodoApp() {
  const [tasks, setTasks] = useState([]);
  // Добавляем задачу не мутируя массив — это важно!
  const addTask = task => setTasks(current => [...current, { id: Date.now(), name: task }]);
  return (
    <>
      <AddTaskForm onAdd={addTask} />
      <TaskList tasks={tasks} />
    </>
  );
}

Что здесь происходит:

  • Пока не добавляются новые задачи, рендерится только форма;
  • TaskList обернут в React.memo, и если массив tasks не изменился, он не рендерится заново;
  • При добавлении addTask создает новый массив, чтобы сработала оптимизация по ссылке.

Разница между клиентским и серверным рендерингом

В React вы можете использовать и клиентский, и серверный рендеринг:

  • Клиентский рендеринг (CSR): Рендер происходит в браузере пользователя. Это классический подход: начальная загрузка быстрая, но индексация поисковиками хуже.
  • Серверный рендеринг (SSR): HTML формируется на сервере (например, с помощью Next.js или Remix) и отправляется браузеру уже в собранном виде. Преимущества — SEO, быстрая “первая отрисовка”.

Также есть гибридные подходы: статическая генерация, инкрементальные обновления страниц и т.п.

Заключение

Рендеринг в React — не просто банальное отображение данных. Это целая система, направленная на высокую производительность, отзывчивость и масштабируемость современных приложений. Вам важно понимать, как работают Virtual DOM, алгоритмы сравнения, что инициирует рендер и как оптимизировать его с помощью хуков и инструментов React. Это не только экономит ресурсы, но и позволяет создавать более быстрые и стабильные приложения.

Понимание рендеринга - важный аспект разработки. Для создания сложных приложений необходимо уметь управлять состоянием и организовывать роутинг. Рассмотрите курс Основы React, React Router и Redux Toolkit, чтобы узнать больше. В первых 3 модулях уже доступно бесплатное содержание — начните погружаться в основы React уже сегодня.

Частозадаваемые технические вопросы и ответы

Как предотвратить лишние рендеры при передаче функций в props?

Если функция создаётся при каждом рендере компонента-родителя, дочерний компонент будет ререндериться даже если props не изменились. Оберните функцию в useCallback, указывая зависимости по необходимости.
jsx const onClick = useCallback(() => { // Обработчик клика }, []); Это позволит сохранить ссылочную целостность функции между рендерами.

Почему key нельзя использовать по индексу в списках?

Если использовать индекс массива как ключ, при изменении порядка элементов или их удалении React будет некорректно сопоставлять элементы, что приведёт к багам в интерфейсе (например, неправильное сохранение состояния дочерних компонентов). Используйте уникальные и постоянные id для ключей, если это возможно.

Как "видит" React разницу между setState и прямым изменением state?

Если вы напрямую меняете state объекта, React не "узнает", что его нужно ререндерить. Изменяйте только с помощью функций обновления (setState, setValue в useState).
Плохой пример: jsx state.value = 42; // Рендер не произойдёт! Правильный способ: jsx setState({ value: 42 }); // Инициируется ререндер

Можно ли рендерить компонент только при изменении определенного props?

Да, для этого воспользуйтесь React.memo c функцией сравнения.
jsx const MyComp = React.memo( function MyComp({ importantProp }) { return <>{importantProp}</>; }, (prev, next) => prev.importantProp === next.importantProp ); Так компонент будет обновляться только при изменении указанного свойства.

Как отследить причину рендера компонента?

Установите инструмент why-did-you-render — он покажет в консоли браузера причину ререндера компонентов в вашем приложении. Это очень удобно для отладки и оптимизации.

Что такое props в React и как их правильно использоватьСтрелочка вправо

Постройте личный план изучения React до уровня Middle — бесплатно!

React — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по React

Открыть базу знаний

Лучшие курсы по теме

изображение курса

React и Redux Toolkit

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий