Перейти к содержанию

React

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

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

React не равен браузеру и не равен DOM. Нужно разделять:

  • react — ядро, которое умеет описывать UI, считать дерево компонентов и управлять обновлениями;
  • react-dom — рендерер для браузера;
  • react-native — рендерер для мобильных платформ;
  • другие рендереры — для CLI, canvas, WebGL и других сред.

React не является полным фреймворком. Сам по себе он решает в первую очередь слой UI:

  • компоненты;
  • состояние;
  • обновление интерфейса;
  • реактивный рендер.

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

Базовая модель React

Интерфейс собирается из компонентов. Компонент — это функция, которая: - получает входные данные - вычисляет, что нужно показать - возвращает JSX

Компоненты можно вкладывать друг в друга и собирать из них все приложение.

Props — это входные данные компонента от родителя. Их свойства: - приходят сверху вниз - считаются внешними по отношению к компоненту - сам компонент не должен их мутировать

function Button({ title, onClick, children, ...props }) {
  return (
    <button onClick={onClick} {...props}>
      {title}
      {children}
    </button>
  );
}
  • children — специальный prop для вложенного содержимого;
  • ...props — способ пробросить остальные свойства дальше, например в DOM-элемент.

State — это внутреннее состояние компонента. Если props приходят извне, то state живет внутри компонента и меняется через React API. Изменение state не меняет DOM напрямую. Вместо этого React запускает новый цикл рендера.

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

  return (
    <button onClick={() => setCount(count + 1)}>
      {count}
    </button>
  );
}

В React данные обычно идут сверху вниз:

  • родитель передает props ребенку;
  • ребенок может уведомить родителя через callback;
  • родитель меняет state;
  • React заново пересчитывает UI.

Схема:

state родителя
  -> props ребенку
  -> действие пользователя
  -> callback вверх
  -> обновление state
  -> новый рендер

Это делает поведение интерфейса более предсказуемым.

JSX и React-элементы

JSX — это синтаксический сахар над вызовами создания React-элементов.

То есть:

<Button title="Save" />

это не HTML и не шаблонный язык. После трансформации это становится вызовом функции из React runtime.

В современном виде примерно так:

jsx(Button, { title: "Save" })

или

jsxs("div", { children: [...] })

3.2. Что такое React-элемент

React-элемент — это обычный JavaScript-объект, который описывает будущий UI.

Упрощенно:

{
  type: Button,
  key: "42",
  props: {
    title: "Save"
  }
}

Важно:

  • React-элемент не является DOM-узлом;
  • это только описание того, что React хочет видеть;
  • элементы создаются заново при каждом рендере.

key

key нужен React, чтобы правильно сопоставлять элементы в списках и при перестановках.

Плохо:

items.map((item, index) => <Row key={index} item={item} />)

Хорошо:

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

Стабильный key помогает React понять:

  • что нужно обновить;
  • что нужно сохранить;
  • что нужно удалить;
  • что просто переместилось.

Ядро React и рендереры

React специально отделяет: - логику вычисления UI; - применение этих вычислений к конкретной платформе.

React core - знает, что нужно показать

Renderer - знает, как это показать в конкретной среде

Рендерер Где используется Что делает
react-dom браузер работает с DOM, hydration, SSR API
react-native iOS / Android работает с нативными view-компонентами
custom renderer CLI / canvas / WebGL / нестандартные среды применяет React-модель к своей платформе

Компоненты React сами по себе не обязаны знать о DOM.

Но если внутри компонента ты используешь:

  • window
  • document
  • DOM API

то ты уже завязываешь код на web-среду.

Поэтому полезно различать:

  • платформенно-независимую логику;
  • код, завязанный на браузер.

React и DOM

DOM — это объектное представление HTML-документа в браузере. Браузер строит DOM-дерево из HTML.

Есть три разных сущности: 1. React-элементы 2. Fiber-дерево 3. Реальный DOM

Виртуальный DOM полезен как упрощенная метафора, но технически в React лучше различать два уровня:

  1. React-элементы — декларативный план интерфейса
  2. Fiber-дерево — рабочая структура React, в которой хранится состояние вычисления

Поэтому фраза "React сравнивает virtual DOM" полезна для общего понимания, но внутри React все устроено точнее.

Fiber — это внутренняя структура React, по одному рабочему узлу на каждый элемент дерева интерфейса. Fiber хранит: - тип компонента; - key; - текущие props; - предыдущие props; - текущее состояние; - связи с родителем, ребенком и соседями; - пометки о том, что нужно сделать на commit.

React поддерживает две версии дерева:

  • current — то, что уже показано пользователю;
  • workInProgress — новое дерево, которое React сейчас рассчитывает.

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

Fiber дал React возможность:

  • считать обновления по частям;
  • приостанавливать вычисления;
  • продолжать позже;
  • расставлять приоритеты;
  • не блокировать интерфейс тяжелой работой.

То есть Fiber — это не "копия DOM", а внутренняя структура управления работой React.

Reconciler — это часть React, которая сравнивает: - старое описание UI; - новое описание UI.

И решает:

  • что обновить;
  • что оставить;
  • что удалить;
  • что смонтировать заново.

React смотрит прежде всего на:

  • тип элемента;
  • key.

Если тип и key совпадают, React старается обновить существующий узел.

Если тип или key поменялись, React считает, что это уже другой элемент:

  • старый узел размонтируется;
  • новый будет смонтирован заново.

При неправильных ключах можно получить:

  • потерю фокуса;
  • лишние размонтирования;
  • сброс локального state;
  • лишние DOM-операции.

Render phase и commit phase

React обновляет UI в две разные фазы

Render phase

Это чистая вычислительная фаза. В render phase React: - вызывает функцию компонента; - читает props, state, context; - выполняет useMemo и useCallback; - строит новое дерево элементов; - обновляет Fiber-дерево; - помечает, что надо изменить.

В этой фазе нельзя делать побочные действия: - нельзя мутировать DOM; - нельзя запускать ручные подписки; - нельзя рассчитывать на то, что render вызовется только один раз.

Важно: - render может быть повторен; - render может быть прерван; - render может быть отброшен без commit.

Commit phase

Это фаза применения изменений. В commit phase React: - создает, удаляет и перемещает реальные DOM-узлы; - обновляет атрибуты и обработчики; - обновляет ref; - запускает useLayoutEffect; - после кадра запускает useEffect.

Commit короче и критичнее по времени, потому что именно после него пользователь видит результат.

state/props changed
    > render phase
        > вычислить новое дерево
        > найти различия
    > commit phase
        > применить изменения в DOM
        > layout effects
        > paint
        > passive effects

Очень важно не путать три разных процесса:

  1. render в React
  2. commit в React
  3. paint в браузере

React render - Это пересчет дерева компонентов и JSX. Пользователь этого еще не видит.

React commit - Это момент, когда React реально меняет DOM.

Browser paint - Это уже работа браузера: - style calculation; - layout; - paint; - compositing.

Упрощенно:

React render
  -> React commit
      -> browser layout
      -> browser paint

Поэтому фраза "React перерисовал компонент" не означает, что браузер обязательно перерисовал весь экран.

Часто это означает только:

  • React пересчитал компонент;
  • сравнил старое и новое описание;
  • внес небольшой патч;
  • браузер обновил только нужное.

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

Монтирование — это первое появление компонента в дереве. Что происходит: 1. React вызывает компонент, получает JSX 2. React создает DOM-узлы 3. React назначает ref 4. React запускает useLayoutEffect 5. браузер рисует кадр 6. React запускает useEffect

Обновление происходит, если: - изменились props; - изменился state; - изменился context; - изменился key; - родитель заново отрендерил дерево; - внешняя подписка вызвала обновление.

Что происходит: 1. новый render 2. сравнение со старым деревом 3. commit изменений 4. cleanup старых эффектов 5. запуск новых эффектов

Размонтирование — это удаление компонента из дерева. Что происходит: - удаляются DOM-узлы; - выполняются cleanup-функции эффектов; - ref сбрасываются.

Что не является размонтированием: - скрытие через CSS; - пустой контент внутри компонента, если сам компонент остался в дереве.

Что важно помнить

Если в эффекте создаются: - таймер; - подписка; - наблюдатель; - соединение;

то их нужно очищать в cleanup.


Context

Context нужен, чтобы не прокидывать одни и те же props через много промежуточных уровней. Типичные данные для context: - тема; - язык; - текущий пользователь; - настройки приложения.

Пример:

const ThemeContext = createContext("light");

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Page />
    </ThemeContext.Provider>
  );
}
function Button() {
  const theme = useContext(ThemeContext);
  return <button data-theme={theme}>Save</button>;
}

Что важно:

  • при изменении value перерисуются потребители этого context;
  • если засунуть в один context слишком много разнородных данных, можно получить лишние рендеры;
  • полезно дробить context по зонам ответственности.

25. React Hooks

Hooks — это функции, через которые функциональные компоненты получают:

  • состояние;
  • эффекты;
  • context;
  • refs;
  • мемоизацию;
  • доступ к другим возможностям React.

Главное правило хуков:

  • вызывать только на верхнем уровне компонента;
  • не вызывать внутри циклов, условий и вложенных функций;
  • или вызывать внутри своих custom hooks.

25.1. useState

Локальное состояние компонента.

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

25.2. useEffect

Побочные эффекты после commit.

25.3. useLayoutEffect

Синхронная работа после DOM-мутаций, но до показа кадра.

25.4. useRef

Доступ к DOM и хранение мутабельного значения без ререндера.

25.5. useMemo

Мемоизация дорогого вычисления.

const filtered = useMemo(() => {
  return items.filter(x => x.visible);
}, [items]);

25.6. useCallback

Мемоизация функции.

const handleClick = useCallback(() => {
  onSave(id);
}, [onSave, id]);

25.7. useContext

Чтение значения context.

25.8. Когда не надо мемоизировать все подряд

useMemo и useCallback полезны не сами по себе, а когда:

  • действительно есть дорогая операция;
  • или важна стабильность ссылки для оптимизации дочерних компонентов.

Иначе это может только усложнять код.


26. Производительность и диагностика

Оптимизация в React — это не "сделать меньше JSX", а уменьшить лишнюю работу там, где она реально есть.

26.1. Что помогает

  • правильные key;
  • нормальная структура state;
  • разделение компонентов;
  • React.memo;
  • useMemo;
  • useCallback;
  • startTransition;
  • useDeferredValue;
  • виртуализация длинных списков;
  • профилирование.

26.2. React.memo

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

26.3. Profiler и DevTools

Для диагностики лучше не гадать, а смотреть:

  • кто рендерится;
  • почему рендерится;
  • сколько это стоит по времени.

27. Роутинг в React-приложении

В исходном документе эта тема упоминалась как заметка на будущее, поэтому здесь коротко фиксирую место темы в общей картине.

Для роутинга в React обычно используют react-router-dom.

Он решает:

  • соответствие URL и экранов;
  • навигацию без полной перезагрузки;
  • параметры маршрута;
  • вложенные маршруты;
  • редиректы;
  • защиту маршрутов.

Типичные сущности:

  • маршруты;
  • Link;
  • Navigate;
  • useParams;
  • useNavigate.

В старых материалах можно встретить useHistory, но в современном React Router чаще используют useNavigate.


28. Архитектура React под капотом в одной схеме

Компоненты
  -> JSX
  -> React-элементы
  -> Fiber-дерево
  -> reconciler сравнивает старое и новое дерево
  -> render phase вычисляет изменения
  -> commit phase применяет изменения
  -> react-dom обновляет DOM
  -> браузер делает paint

Или еще короче:

данные меняются
  -> React пересчитывает UI
  -> React находит минимальный патч
  -> DOM обновляется точечно
  -> пользователь видит новый экран

29. Короткая ментальная модель

Правильнее всего думать о React так:

  1. интерфейс — это функция от состояния;
  2. JSX — это описание желаемого UI;
  3. React не рисует браузер сам, а управляет обновлением host-среды через рендерер;
  4. render — это вычисление;
  5. commit — это применение;
  6. DOM обновляется не весь, а только там, где реально есть изменения.

30. Темы, которые еще стоит дописать в будущем

Ниже темы, которые логично добавить в этот конспект следующим этапом.

30.1. По React core

  • StrictMode
  • Portals
  • Fragments
  • memo, forwardRef, lazy
  • useReducer
  • useId
  • useDeferredValue
  • useTransition
  • useSyncExternalStore
  • useImperativeHandle

30.2. По React DOM и браузеру

  • детально про hydration mismatch
  • event replay при гидратации
  • ресурсные подсказки preload, preconnect, preinit
  • отличие layout, paint, reflow, repaint

30.3. По архитектуре приложений

  • container/presentational components
  • feature-based folder structure
  • разделение UI state и server state
  • кастомные хуки как слой композиции логики

30.4. По данным и серверу

  • fetch patterns подробнее
  • race conditions и отмена запросов
  • React Query / TanStack Query
  • SWR
  • optimistic updates

30.5. По роутингу

  • react-router-dom подробно
  • nested routes
  • layout routes
  • private routes
  • public routes
  • guards
  • loaders/actions, если использовать data routers

30.6. По формам

  • валидация форм
  • React Hook Form
  • Formik
  • сложные формы с вложенными полями

30.7. По производительности

  • виртуализация списков
  • splitting bundles
  • lazy loading
  • profiling workflows
  • анти-паттерны оптимизации

30.8. По экосистеме

  • Node.js как среда разработки
  • Vite
  • Webpack
  • Babel
  • TypeScript + React
  • HMR
  • dev server

30.9. По серверным возможностям

  • SSR на практике
  • streaming SSR на практике
  • React Server Components подробнее
  • Next.js как React-фреймворк поверх этих возможностей

30.10. По тестированию

  • React Testing Library
  • unit tests компонентов
  • integration tests UI
  • тестирование хуков
  • тестирование роутинга и асинхронного UI