Skip to content

Instantly share code, notes, and snippets.

@SeLub
Created February 3, 2026 16:42
Show Gist options
  • Select an option

  • Save SeLub/1d8f7cb47d696f01f4eb351afca1b59d to your computer and use it in GitHub Desktop.

Select an option

Save SeLub/1d8f7cb47d696f01f4eb351afca1b59d to your computer and use it in GitHub Desktop.

Основы современного React

Содержание

  1. Основные понятия React
  2. Нативные хуки React
  3. Фазы рендеринга в React
  4. Жизненный цикл компонента
  5. Точка входа в React-приложение
  6. Реконсиляция
  7. Рендеры
  8. Fiber и useTransition
  9. Поверхностное сравнение
  10. Инструменты оптимизации

Основные понятия React

React — это библиотека JavaScript для создания пользовательских интерфейсов. Основные концепции:

  • Компоненты — независимые и повторно используемые блоки UI, которые логически разделяют интерфейс
  • JSX — синтаксический сахар, позволяющий писать HTML-подобный код в JavaScript
  • Виртуальный DOM — легковесное представление реального DOM, которое позволяет React эффективно обновлять интерфейс
  • Состояние (state) — данные, которые могут изменяться в результате взаимодействия пользователя или других факторов
  • Свойства (props) — данные, передаваемые в компонент извне, доступные только для чтения
  • Побочные эффекты — действия, выходящие за пределы рендеринга компонента (API-запросы, подписки, таймеры)

Нативные хуки React

Хуки — это специальные функции, позволяющие использовать состояние и другие возможности React без написания классов.

Базовые хуки

  • useState — позволяет добавить состояние в функциональный компонент
  • useEffect — позволяет выполнять побочные эффекты в функциональных компонентах
  • useContext — позволяет подписываться на изменения контекста

Дополнительные хуки

  • useReducer — альтернатива useState для сложной логики управления состоянием
  • useCallback — мемоизирует функцию между рендерами
  • useMemo — мемоизирует результат вычислений между рендерами
  • useRef — позволяет получить доступ к DOM-элементу или хранить изменяемое значение
  • useImperativeHandle — позволяет настраивать экземпляр, передаваемый родительским компонентам
  • useLayoutEffect — синхронная версия useEffect, выполняется после измерения и расположения
  • useDebugValue — позволяет отображать метку для пользовательских хуков в React DevTools

Хуки для оптимизации

  • useTransition — позволяет пометить обновления как "неблокирующие", сохраняя интерфейс отзывчивым
  • useDeferredValue — позволяет отложить обновление части UI при изменении данных
  • useId — генерирует уникальный ID для доступности

Фазы рендеринга в React

Рендеринг в React — это процесс обновления интерфейса, состоящий из трех основных этапов: триггера (инициация), рендеринга (вычисление изменений в Virtual DOM) и фиксации (применение изменений к реальному DOM). Этот процесс разделен на две основные фазы: фаза рендеринга (render phase, чистая) и фаза фиксации (commit phase, DOM-операции).

Фаза рендеринга (Render Phase) (или фаза согласования/reconciliation)

  • Что происходит: React вызывает компоненты, вычисляет изменения, сравнивая текущее дерево с предыдущим (алгоритм Diffing).
  • Особенности: Эта фаза может быть приостановлена, прервана или перезапущена React. В этой фазе компоненты должны быть "чистыми" (без побочных эффектов).
  • Результат: Создание нового виртуального дерева (Fiber tree) и формирование списка необходимых изменений.

Фаза фиксации (Commit Phase)

  • Что происходит: React применяет вычисленные изменения к реальному DOM-дереву (appendChild, update, remove).
  • Особенности: Эта фаза выполняется синхронно и не прерывается, чтобы гарантировать правильность отображения.
  • Результат: Обновленный пользовательский интерфейс.

Дополнительные стадии

  • Триггер: Событие, инициирующее рендер (первоначальный рендер или изменение стейта/пропсов).
  • После фиксации (Post-commit): После того как DOM обновлен, React вызывает «побочные эффекты» (например, хук useEffect или методы жизненного цикла componentDidMount/componentDidUpdate).

Ключевые моменты

  • Virtual DOM: React не трогает реальный DOM, если результат рендеринга такой же, как в прошлый раз.
  • Ререндеринг: Происходит, когда компонент получает новые props или меняется его state.
  • Оптимизация: React выполняет минимально необходимые операции в DOM.

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

Жизненный цикл компонента React — это последовательность стадий, через которые проходит компонент: монтирование (создание и вставка в DOM), обновление (изменение props/state) и размонтирование (удаление из DOM). В классовых компонентах используются методы (componentDidMount, componentDidUpdate, componentWillUnmount), а в современных функциональных компонентах — хук useEffect.

Основные фазы жизненного цикла (классовые компоненты):

  • Монтирование (Mounting): Компонент создается и добавляется в DOM. Методы: constructor, render, componentDidMount (используется для API-запросов).
  • Обновление (Updating): Вызывается при изменении props или state. Методы: render, componentDidUpdate (обновление данных после изменения).
  • Размонтирование (Unmounting): Компонент удаляется из DOM. Метод: componentWillUnmount (очистка таймеров, подписок).

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

  • Монтирование: useEffect(() => { ... }, []) — пустой массив зависимостей означает выполнение один раз при создании.
  • Обновление: useEffect(() => { ... }, [data]) — выполняется, когда изменяется переменная data.
  • Размонтирование: useEffect(() => { return () => { ... } }, []) — возврат функции очистки.

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

Точка входа

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

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

import ReactDOM from 'react-dom/client';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<div>My React App</div>);

Этот код использует пакет react-dom/client для создания Root-контейнера и, далее, рендера DIV-элемента в этот контейнер. Именно здесь и находится тот самый пусковой механизм React, точнее, здесь их сразу два: создание контейнера посредством createRoot, и запуск процесса рендеринга в контейнере. Но обо всем по порядку.

Реконсиляция

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

Дополнительную сложность процессу создает тот факт, что сегодня существуют разные платформы, где итоговый UI может быть выведен (на экран или, например, в строку или файл). В частности, сам React предусматривает рендеринг в Web, серверный рендеринг (SSR), рендеринг на мобильных устройствах (React Native) и др.

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

  • Mutation mode - для платформ, позволяющих мутировать итоговое дерево (т.е. имеют методы, схожие с appendChild/removeChild)
  • Persistent mode - для платформ с иммутабельными деревьями. В этом случае, на каждое изменение, все дерево клонируется целиком, производятся модификации, а затем все дерево полностью заменяется на модифицированное

Сам этот пакет не осуществляет конечную привязку к DOM, а только обеспечивает всю необходимую механику по подготовке и манипуляции элементами. Сама же непосредственная привязка к DOM осуществляется средствами внешнего провайдера, реализующего API пакета react-reconciler. Реализации провайдером заключается в выставлении конкретных флагов-настроек и описании методов-колбэков, таких как createInstance, appendChild, removeChild и др.. Такой подход позволяет создавать разные провайдеры для разных случаев и платформ.

Рендеры

Провайдеры, реализующие API пакета react-reconciler, условно, называются рендеры. Теоретически, пакет react-reconciler задумывался как полноценный API, но, с момента его создания в 2017-м работы по нему ведутся довольно активно, периодически случаются существенные изменения, поэтому, формально, пакет считается unstable, использовать его напрямую в своих проектах стоит с осторожностью.

Сам же React предлагает несколько реализаций рендеров "из коробки":

  • React DOM - этот рендер мы уже видели в примере выше. Он обеспечивает привязку к DOM-дереву браузера
  • React Native - этот рендер обеспечивает нативный рендеринг на мобильных платформах
  • React ART - позволяет рисовать векторную графику средствами React. Фактически, является реактивной оболочкой для библиотеки ART.

Fiber и useTransition

Fiber

Прежде чем двинуться дальше, важно познакомиться с базовой сущностью движка React.

Fiber - это внутренний объект React, представляющий задачу ("работу"), которую движок запланировал к выполнению или уже выполнил.

React Fiber — это низкоуровневый алгоритм движка (реконсилер), разбивающий рендеринг на части для отзывчивости. useTransition — это хук, использующий Fiber для пометки обновлений состояния как «менее важных», позволяя держать интерфейс отзывчивым (например, при фильтрации больших списков). Fiber — движок, useTransition — инструмент управления приоритетами.

Ключевые отличия:

  • Суть: Fiber — это переписанный алгоритм согласования (reconciliation), работающий с Fiber-узлами (структура данных), а useTransition — API-хук.
  • Уровень: Fiber работает «под капотом» (автоматически), а useTransition используется разработчиком явно для оптимизации.
  • Функция: Fiber обеспечивает возможность приостанавливать, прерывать и возобновлять рендеринг. useTransition явно разделяет обновления на срочные (input) и фоновые (результаты поиска).
  • Применение: Fiber работает всегда в React 16+. useTransition применяется для конкретных задач производительности в React 18+.

Пример использования useTransition:

const [isPending, startTransition] = useTransition();
// ...
// Срочное обновление (ввод)
setInputValue(e.target.value);
// Фоновое обновление (фильтрация)
startTransition(() => {
  setSearchQuery(e.target.value);
});

useTransition помогает избежать зависания интерфейса при тяжелых рендерах, позволяя пользователю взаимодействовать с элементами.

Поверхностное сравнение

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

Основные аспекты:

  • Примитивы (Number, String, Boolean): Сравниваются по значению (a===b).
  • Объекты/Массивы: Сравниваются ссылки. Если объект создан заново (новый {}), сравнение вернет false, даже если поля идентичны.
  • Где применяется: Используется в React.memo для пропов, в массивах зависимостей useEffect и useMemo, а также при обновлении стейта.
  • Цель: Оптимизация производительности: предотвращение перерисовки компонентов, если данные не изменились.

Важно:

Для корректной работы поверхностного сравнения необходимо использовать принцип неизменяемости (immutability) — создавать новые объекты/массивы при изменении, а не мутировать старые.

Инструменты оптимизации

React.memo

React.memo — это функция высшего порядка (HOC - Higher Order Component), предназначенная для оптимизации производительности функциональных компонентов в React. Она мемоизирует (кеширует) результат рендеринга компонента: если пропсы не изменились, React пропускает перерендер компонента, используя сохраненный результат.

Основные характеристики и использование:

  • Предотвращение лишних рендеров: Полезен, когда компонент часто перерисовывается с теми же пропсами.
  • Поверхностное сравнение: React.memo сравнивает текущие и предыдущие пропсы, используя shallow comparison (поверхностное сравнение, Object.is).
  • Применение: const MemoizedComponent = React.memo(MyComponent);.
  • Особенности: Если пропсы — объекты или функции, они должны быть мемоизированы с помощью useMemo или useCallback, иначе memo будет считать их новыми.
  • Собственный компаратор: Вторым аргументом можно передать функцию, которая принимает prevProps и nextProps и возвращает true, если пропсы равны, и false — если нет.

React.memo не предотвращает рендер, если меняется собственное состояние компонента (useState) или контекст (useContext).

useMemo

useMemo — это React-хук для оптимизации производительности, который кэширует (мемоизирует) результат вычислений между рендерами. Он пересчитывает значение только при изменении указанных зависимостей, предотвращая ресурсоемкие операции при каждом перерендеринге компонента.

Основные характеристики useMemo:

  • Синтаксис: const cachedValue = useMemo(calculateValue, dependencies).
  • Применение: Используется для кэширования тяжелых вычислений, а не для побочных эффектов.
  • Отличие от useCallback: useMemo кэширует результат вызова функции, в то время как useCallback кэширует саму функцию.
  • Отличие от React.memo: React.memo оптимизирует весь компонент, предотвращая перерисовку, если пропсы не изменились, тогда как useMemo оптимизирует конкретные вычисления внутри компонента.

Пример использования:

const sortedList = useMemo(() => {
  return heavySortingFunction(data); // Выполняется только при изменении data
}, [data]);

Когда использовать:

  • При необходимости избежать выполнения «тяжелых» функций при каждом рендере.
  • При передаче мемоизированных значений в дочерние компоненты, обернутые в React.memo.

useCallback

Вероятно, имелся в виду хук useCallback в React, который используется для мемоизации (кэширования) определений функций между рендерами. Он предотвращает пересоздание функции, оптимизируя производительность компонентов, особенно при передаче колбэков в дочерние компоненты, предотвращая лишние перерисовки.

Основные характеристики и использование:

  • Синтаксис: const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]);.
  • Когда использовать:
    • При передаче функций в мемоизированные компоненты (React.memo).
    • Когда функция используется как зависимость в других хуках (например, useEffect).
  • Отличие от useMemo: useCallback кэширует саму функцию (ее определение), а useMemo кэширует результат вычисления функции.

Пример:

import { useCallback } from 'react';

const handleSubmit = useCallback((data) => {
  console.log(data);
}, [/* зависимости */]);

Осторожность: Не следует использовать useCallback везде подряд. Это имеет смысл только при реальных проблемах с производительностью, так как сам хук также требует ресурсов на выполнение.

Сводная таблица инструментов оптимизации

Инструмент Тип Что делает Цель
React.memo HOC Мемоизирует компонент на основе пропсов Избежать рендера, если props не изменились
useMemo Hook Кэширует результат функции Избежать тяжелых вычислений
useCallback Hook Кэширует саму функцию Избежать пересоздания функции (для пропсов)
useTransition Hook Делает обновление состояния неблокирующим Сделать UI отзывчивым при тяжелых рендерах

Подробное отличие

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

  • Применение: Оборачивает функциональный компонент.
  • Как работает: Сравнивает prevProps и nextProps. Если они равны, компонент не перерисовывается.
  • Пример: const MyComponent = React.memo(function MyComponent(props) { ... });

useMemo (Мемоизация значений)

  • Применение: Используется внутри компонента для кэширования результатов, например, при сортировке огромного массива.
  • Как работает: Пересчитывает значение только при изменении зависимостей.
  • Пример: const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

useCallback (Мемоизация функций)

  • Применение: Используется для передачи функций-колбэков в мемоизированные дочерние компоненты (в связке с React.memo).
  • Как работает: Сохраняет ссылку на функцию между рендерами, пока зависимости не изменятся.
  • Пример: const memoizedCallback = useCallback(() => { doSomething(a); }, [a]);

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

  • Применение: Используется, когда нужно обновить состояние, но это вызывает «зависание» UI (например, фильтрация большого списка при вводе).
  • Как работает: Позволяет пометить обновление как «низкоприоритетное» (transition). Интерфейс остается отзывчивым, пока выполняется тяжелая операция.
  • Пример: const [isPending, startTransition] = useTransition();

Резюме по использованию

  • Используйте React.memo для компонентов, которые часто рендерятся с теми же пропсами.
  • Используйте useMemo, если расчеты занимают много времени.
  • Используйте useCallback, если функция передается как проп в memo компоненты.
  • Используйте useTransition для действий, требующих тяжелого обновления UI (поиск, фильтрация).
@SeLub
Copy link
Author

SeLub commented Feb 3, 2026

Я использую useCallback почти только тогда, когда мне нужна функция, которая должна выполняться внутри useEffect, и её нельзя объявить внутри него.

useMemo, с другой стороны, я использую для тяжёлых вычислений, которые я не хочу запускать на каждом рендере, а только когда его результат будет отличаться (то есть, какие-то данные, которые он использует, отличаются от предыдущего рендера).
"Тяжёлым" можно назвать функции, у которых временная сложность больше O(1) или O(n*log(n)). Например, вложенные циклы. Или любые функции типа reduce, filter и т.д. В этом посте это лучше всего объяснено https://www.developerway.com/posts/how-to-use-memo-use-callback

Ещё добавлю, что я использую useCallback, когда передаю коллбэки как пропсы дочерним компонентам.

@SeLub
Copy link
Author

SeLub commented Feb 3, 2026

Полезно знать, что новая версия React 19 будет включать этап компиляции, который сам будет делать мемоизацию, и, соответственно, эти хуки устареют.
Тем не менее, концепция довольно простая. Твоё React-дерево будет перерисовывать каждый дочерний элемент при обновлении. Если ты хочешь избежать повторного запуска некоторых функций, например, ресурсоёмких, то можешь мемоизировать/кешировать функцию (useCallback) или возвращаемое значение (useMemo) и вручную передавать зависимости, чтобы сказать React, когда можно пересчитать.

React 19 действительно представляет React Compiler, который автоматически мемоизирует компоненты и хуки, снижая необходимость в ручном использовании useMemo и useCallback. Хотя эти хуки не исчезнут совсем, необходимость в них значительно сократится, так как компилятор оптимизирует код самостоятельно. Это улучшает производительность, особенно в сложных компонентах, без ручной оптимизации.

Ключевые моменты перехода:

  • Автоматическая мемоизация: Компилятор самостоятельно анализирует код и кэширует значения, делая useMemo, useCallback и React.memo менее востребованными
  • Совместимость: Компилятор работает с существующим кодом, поэтому удалять старые хуки не обязательно сразу — он справится с ними, оптимизируя приложение
  • Фокус на логике: Разработчики могут сосредоточиться на функциональности, а не на ручной оптимизации рендеринга.
  • Новые хуки React 19: Включают use (для Promise и Context), а также улучшенные формы (useActionState, useFormStatus)

Тем не менее, useMemo и useCallback могут оставаться полезными в специфических, редко встречающихся ситуациях, где компилятору требуются подсказки React 19 действительно представляет React Compiler, который автоматически мемоизирует компоненты и хуки, снижая необходимость в ручном использовании useMemo и useCallback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment