React 19, стабильный с декабря 2024, - самое значимое изменение API surface со времени введения Hooks в 16.8. Релиз вводит пять новых примитивов, которые вместе переключают mutation model React от component-external store паттерна (Redux, useState + useEffect + fetch) к action-based модели, осведомлённой о сервере, совместимой с Progressive Enhancement и тесно интегрированной с concurrent renderer. В этой статье разбираю каждый примитив на уровне механизма - как он работает изнутри и когда применять.
Часть I - useOptimistic: конкурентная composition состояния
useOptimistic - ответ React 19 на специфическую UX-проблему: разрыв между тем, когда пользователь совершает действие, и когда сервер его подтверждает.
- Сигнатура:
const [optimisticState, addOptimistic] = useOptimistic(state, updateFn).state- авторитетное серверное состояние.updateFn(currentState, optimisticValue) => newState- чистая функция, вычисляющая как должно выглядеть состояние *если действие успешно*.addOptimistic(value)- немедленное применение оптимистичного обновления. - Механизм render-phase composition:
addOptimistic(value)не сохраняет значение в отдельную state-переменную - ставит в очередь обновление. При следующем рендере React применяетupdateFn(currentState, value). Пока async-действие pending, каждый рендер возвращает скомпонованный оптимистичный state. Когда действие выбрасывает ошибку, React автоматически откатывается к состояниюstateдо вызоваaddOptimistic- ручного rollback не требуется. - Несколько конкурентных оптимистичных обновлений: Три вызова
addOptimisticприменяются в порядке:updateFn(updateFn(updateFn(baseState, v1), v2), v3). У каждого pending-действия независимая обработка ошибок. - Эквивалент React 18:
useState(false) + useTransition + try/catch + setOptimistic(false).useOptimisticзаменяет этот паттерн одним вызовом хука с автоматическим rollback.
Часть II - Хук use(): условное чтение ресурсов
- use(promise) - Suspense-интеграция в Client Components:
const data = use(promise). Если Promise pending - React приостанавливает компонент (ближайший Suspense boundary). В отличие отuseEffect + useState, нет промежуточного рендера сnull- компонент рендерится ровно один раз с resolved значением. Устраняетif (!data) return <Loading />паттерн. - use(promise) vs async Server Components: В Server Component используйте
awaitнапрямую.use()- для Client Components, читающих Promise, переданный как prop от Server Component. Server Component инициирует fetch (не await-ит), передаёт Promise в Client Component; Client Component вызываетuse(promise). Fetch стартует на сервере, Suspense-граница на клиенте. - use(context) - условный доступ к Context: Функционально эквивалентен
useContext(), но может быть вызван внутри conditional-блоков и циклов - в отличие отuseContext, который нарушает Rules of Hooks при условном вызове.if (isAdmin) { const config = use(AdminContext); }- теперь валидный паттерн.
Часть III - Server Actions: RPC-over-HTTP контракт
Директива 'use server' на async-функции трансформирует её в server-side операцию, доступную с клиента без написания Route Handler или API-эндпоинта.
- Механизм на уровне фреймворка: Next.js присваивает каждой
'use server'-функции уникальный action ID (хеш пути модуля + имени экспорта). При вызове с клиента React сериализует аргументы и отправляет HTTP POST с action ID. Сервер ищет ID в реестре, десериализует аргументы и выполняет функцию. - Progressive Enhancement через `<form action={serverAction}>`: Сабмит формы работает без JavaScript - стандартный HTTP POST. При наличии JS React перехватывает сабмит и обрабатывает как streaming RSC update.
- CSRF-защита: React генерирует request token, привязанный к текущей сессии/origin, валидируемый server-side перед выполнением action. Автоматически, без ручного управления CSRF-токенами.
- Безопасность - обязательная server-side валидация: Аргументы Server Action - это контролируемые пользователем данные HTTP POST. Никогда не доверяйте им:
const user = await getSession(); if (item.userId !== user.id) throw new Error('Unauthorized');.
Часть IV - useActionState, useFormStatus и синтаксические изменения
- useActionState (переименован из useFormState):
const [state, dispatch, isPending] = useActionState(action, initialState). Action:(previousState, formData) => Promise<newState>- threading предыдущего состояния.isPending: trueпока action выполняется. ЗаменяетuseState + setLoading(false/true)паттерн. - useFormStatus:
const { pending } = useFormStatus(). Читает статус ближайшей родительской<form>сaction. Должен вызываться в компоненте-потомке формы (не в самом компоненте формы).<SubmitButton>паттерн:const { pending } = useFormStatus(); return <button disabled={pending}>... - ref как prop (forwardRef устарел): В React 19
ref- обычный prop в function components.function Input({ ref, ...props }) { return <input ref={ref} {...props} />; }безforwardRef.React.forwardRefпродолжает работать, но выводит deprecation warning. - Context как provider:
<ThemeContext value={theme}>вместо<ThemeContext.Provider value={theme}>. Старый синтаксис продолжает работать. - Cleanup-функции из ref callbacks:
<div ref={(node) => { ...; return () => cleanup(); }}>- React вызывает cleanup при анмаунте или переназначении ref.
Часть V - Action-based mutation архитектура: полная компоновка
Все пять примитивов React 19 компонуются в единый паттерн мутации, заменяющий useState + useEffect + fetch + ручной loading state + ручной rollback.
- Полная компоновка: (1) Server Action - серверная функция мутации; (2)
useActionState(action, initial)- persistent state +isPending; (3)useOptimistic(serverState, updateFn)- немедленный UI-фидбэк; (4)useFormStatusв<SubmitButton>- disabled кнопка при pending. Эти четыре примитива заменяют Zustand store сloading + error + optimisticфлагами +fetchвuseEffect+try/catchrollback. - Пример: Add to Cart в headless Shopify:
const [cart, addToCart, isPending] = useActionState(addToCartAction, initial); const [optimisticCart, addOptimistic] = useOptimistic(cart, (s, line) => ({ ...s, lines: [...s.lines, line] }));. Счётчик корзины обновляется мгновенно; при ошибке Shopify API - автоматический rollback. Контекст архитектуры - в гайде по headless Shopify. - PPR + useOptimistic: PPR обслуживает статический shell с CDN; dynamic holes - интерактивные островки с
useOptimistic. Подробнее - в гайде по PPR. - Когда action model НЕ заменяет внешние стейт-менеджеры: Drag-and-drop state, multi-step wizard, real-time WebSocket-driven state - по-прежнему
useReducer, Zustand, XState. Action model превосходит именно в server mutation flows.
Часть VI - Миграция и изменения React 18 → 19
- `useFormState` → `useActionState`: Переименован, перенесён из
react-domвreact, добавленisPendingтретьим возвращаемым значением. - `ReactDOM.render` и `ReactDOM.hydrate` удалены: Заменить на
createRootиhydrateRoot(API React 18). - TypeScript: Обновить
@types/reactдо 19.x. Ref-prop в function components безforwardRef, типы дляuse(),useOptimistic,useActionState,useFormStatus. - Метаданные документа:
<title>,<meta>,<link>внутри компонентов автоматически hoisting в<head>. В Next.js App Router предпочтительнееgenerateMetadata()для SSR. - Asset preloading API:
import { preload, preconnect, prefetchDNS } from 'react-dom'.preload('/font.woff2', { as: 'font', ... })- дедуплицированные<link rel='preload'>hints.
Заключение
React 19 - согласованная архитектурная концепция: мутации описываются как действия (функции, принимающие вход и возвращающие новое состояние), а фреймворк управляет async-жизненным циклом (pending state, оптимистичные обновления, rollback ошибок). Для команд на Next.js App Router action model и Server Actions - предназначенная mutation архитектура. Для архитектурного ревью или реализации - сервис React SPA development | кейсы | обсудить проект.
Источники
- React Team. 'React 19 Release Notes': https://react.dev/blog/2024/12/05/react-19
- React Team. 'useOptimistic Reference': https://react.dev/reference/react/useOptimistic
- React Team. 'use Reference': https://react.dev/reference/react/use
- React Team. 'useActionState Reference': https://react.dev/reference/react/useActionState
- React Team. 'useFormStatus Reference': https://react.dev/reference/react-dom/hooks/useFormStatus
- React Team. 'Server Actions': https://react.dev/reference/rsc/server-actions
- Next.js Team. 'Server Actions and Mutations': https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations
- React Team. 'React 19 Upgrade Guide': https://react.dev/blog/2024/04/25/react-19-upgrade-guide
