Веб-разработка

React: основы

FaxJS за одну ночь

Facebook Timeline - один из самых сложных интерфейсов своего времени: реальное время, бесконечный скролл, разные типы контента. jQuery-код стал неуправляемым: изменение одного компонента непредсказуемо ломало другие. Инженер Adam Moss написал "FaxJS" - прототип на основе идеи UI = f(state), где весь интерфейс это функция от данных. В 2013 году React был представлен на JSConf US. Аудитория встретила его скептически: "HTML в JavaScript? Серьёзно?" Через три года React стал стандартом индустрии, а через пять - компонентная модель перекочевала в Vue 3, Angular, Svelte и мобильную разработку (React Native).

Компонентная модель React изменила не только фронтенд - она повлияла на архитектуру мобильных приложений (React Native), десктопных (Electron + React) и серверного рендеринга (Next.js, Remix).

Virtual DOM и компонентная модель

2011 год. Facebook запускает Timeline - и инженер Adam Moss смотрит на 10 000 строк jQuery-спагетти, где изменение одного поля ломает три несвязанных блока. За ночь он пишет "FaxJS" - прототип системы, где UI это функция от состояния: `UI = f(state)`. Через два года Facebook откроет React, и фронтенд никогда не станет прежним.

Центральная идея React - компонент. Не виджет, не модуль, не класс в классическом смысле. Компонент - это функция, которая принимает данные (props) и возвращает описание UI. Ничего больше. Вся сложность приложения - это дерево таких функций.

Но компоненты сами по себе - ещё не революция. Революция - Virtual DOM. Проблема с прямой манипуляцией DOM через `document.querySelector` в том, что каждое изменение заставляет браузер пересчитывать layout, repaint, recomposite. Это дорого. React делает иначе: сначала строит виртуальное дерево объектов в памяти (Virtual DOM), сравнивает его с предыдущим состоянием, и только минимально необходимые изменения отправляет в реальный DOM. Это как диффинг git-коммитов - не перезаписывает весь файл, только изменённые строки.

Однонаправленный поток данных - третий кит React. Данные идут только вниз: от родительского компонента к дочерним через props. Дочерний компонент никогда не изменяет props напрямую - он сообщает об изменении через callback-функцию, переданную сверху. Это кажется ограничением, но на практике делает приложение предсказуемым: в любой момент понятно, откуда пришли данные и кто несёт ответственность за их изменение. Vue 3, Solid, Svelte - все переняли эту модель.

Что делает Virtual DOM в React?

JSX и reconciliation

JSX выглядит как HTML внутри JavaScript - и именно это сбивает с толку большинство новичков. JSX - это не строка, не шаблон и не магия браузера. Это синтаксический сахар над вызовами функций. Babel (или TypeScript) при сборке превращает каждый JSX-тег в вызов `React.createElement()`, который возвращает обычный JavaScript-объект.

Reconciliation - это алгоритм, которым React сравнивает два Virtual DOM дерева. Наивный diff двух деревьев - O(n^3). React использует эвристики и делает это за O(n): если тип узла изменился (например, `div` стал `span`) - удалить и создать заново; если тип тот же - обновить атрибуты. Ключи (`key`) помогают React понять какой элемент в списке соответствует какому при перестановках.

Key - это не prop для компонента. React снимает его до того как передаёт props. Частая ошибка - использовать индекс массива как key: `key={index}`. При перестановке элементов индексы смещаются, React путается и делает ненужные re-render или теряет state. Правило: key из стабильного ID данных, никогда из индекса (если список не статичный).

React 18 ввёл Concurrent Mode и Fiber-архитектуру. Reconciliation теперь прерываемый: React может остановить рендеринг на полпути, заняться более приоритетной задачей (например, анимацией) и вернуться. Это основа useTransition и Suspense.

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

Hooks: useState и useEffect

До 2019 года state в React был привилегией классовых компонентов. Функциональный компонент - красивая штука, но без памяти: вызвали, вернул JSX, забыл всё. Hooks изменили это. `useState` даёт функциональному компоненту способность помнить значение между рендерами. Под капотом React хранит hooks в связном списке, привязанном к конкретной позиции компонента в дереве - поэтому нельзя вызывать хуки в условиях или циклах: порядок должен быть детерминированным каждый рендер.

Вызов `setCount` не изменяет переменную немедленно. Он говорит React: "при следующем рендере дай компоненту это новое значение". React батчит несколько вызовов set из одного event handler в один рендер-цикл. Это значит, что если вызвать `setCount(count + 1)` три раза подряд в одном обработчике - счётчик вырастет на 1, а не на 3. Функциональная форма `setCount(prev => prev + 1)` решает эту проблему: она всегда работает с актуальным значением.

`useEffect` - мост между чистым рендером и побочными эффектами: запросы к API, подписки, таймеры, изменения заголовка страницы. Без `useEffect` эти операции либо выполнялись бы при каждом рендере, либо требовали классовых методов жизненного цикла. Хук принимает функцию и массив зависимостей.

Три режима `useEffect` по массиву зависимостей: пустой массив `[]` - выполнить один раз после первого рендера (аналог componentDidMount); конкретные переменные `[dep1, dep2]` - выполнить при монтировании и при изменении зависимостей; без массива вообще - выполнять при каждом рендере (почти всегда ошибка).

React DevTools показывает дерево компонентов и текущий state/props каждого - незаменимо при дебаге. В production-билде React включает дополнительные оптимизации и убирает dev-только предупреждения.

useState обновляет переменную немедленно после вызова set-функции

Вызов set-функции только планирует следующий рендер с новым значением; в текущем рендере переменная остаётся старой

React - это функция `UI = f(state)`. Каждый рендер - это отдельный снимок с замороженными значениями. Если нужно читать новое значение сразу - использовать функциональное обновление `prev => prev + 1` или useReducer.

Что произойдёт, если вызвать `setCount(count + 1)` три раза подряд в одном event handler?

Ключевые идеи

  • **Компонент = функция от props:** UI строится как дерево маленьких функций, каждая отвечает за свой фрагмент интерфейса
  • **Virtual DOM:** React держит копию UI в памяти JS, диффит её и отправляет в реальный DOM только минимальный патч - дорогие browser reflow только там, где реально нужно
  • **JSX компилируется в React.createElement():** никакой магии - просто синтаксический сахар над вызовами функций, проверяемый TypeScript
  • **useState планирует рендер, не изменяет немедленно:** каждый рендер - снимок со своими значениями; для накопительных обновлений - функциональная форма `prev => ...`
  • **useEffect для побочных эффектов:** fetch, subscriptions, timers - всё вне рендер-функции, с явными зависимостями и cleanup

Связанные темы

React - это слой view. Вокруг него строится экосистема:

  • DOM и браузерные API — Virtual DOM абстрагирует прямую работу с реальным DOM
  • State Management — Redux, Zustand, Jotai - следующий уровень управления состоянием
  • SSR и Next.js — Server Components и SSG строятся поверх компонентной модели React
  • Vue и Angular — Альтернативные фреймворки с похожей компонентной моделью, другими trade-offs

Вопросы для размышления

  • Если `UI = f(state)` - чистая функция без побочных эффектов, зачем тогда нужен useEffect? Что происходит с этой моделью, когда нужно обратиться к серверу?
  • React батчит обновления state из одного event handler. Почему это хорошая идея с точки зрения производительности - и какие сценарии это усложняет?
  • Virtual DOM добавляет слой абстракции и неизбежно тратит память. В каких случаях прямая манипуляция DOM всё равно будет быстрее React?

Связанные уроки

  • web-03 — JavaScript-основы нужны до React
  • web-04 — DOM-модель - фундамент под Virtual DOM
  • web-07 — Vue и Angular проще воспринимаются после React
  • web-08 — State management - логичное продолжение хуков
  • web-09 — Next.js строится поверх компонентной модели React
  • aie-05-api-integration — Однонаправленный поток данных - та же идея что однонаправленный pipeline в LLM API
  • comp-01-intro
React: основы

0

1

Войти