Skip to content

React 19: useOptimistic, use(), Server Actions - механизм и архитектура (2026)

React 19 - не инкрементальное обновление React 18. Он вводит новую модель мутаций - action-based архитектуру - где Server Actions, useOptimistic, useActionState и useFormStatus компонуются в полный паттерн серверной мутации, устраняя reducer + async middleware стек для большинства form-driven и data-mutation UI.

React 19 новые фичи: useOptimistic, use hook, Server Actions - архитектурная диаграмма
Опубликовано:Обновлено:Время чтения:16 мин

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/catch rollback.
  • Пример: 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 | кейсы | обсудить проект.

Источники

Похожие статьи

Partial Prerendering (PPR) в продакшне: архитектурные паттерны (2026)

Детальный разбор Next.js Partial Prerendering в продакшне. Механизм двухфазного ответа (статический shell с CDN + стриминговые dynamic holes), правила размещения Suspense-границ, взаимодействие с Full Route Cache, дизайн fallback для нулевого CLS, измеренные TTFB/LCP, сравнение с ISR+CSR и full SSR, известные ограничения и фреймворк принятия решений.

Next.jsPerformancePPR
Читать статью

Миграция на Next.js App Router с Pages Router: полный практический гайд (2026)

Практический гайд по миграции продакшн-приложений Next.js с Pages Router на App Router. Стоимостная модель гидратации RSC, механика модульного графа webpack и директивы 'use client', внутреннее устройство Data Cache, инкрементальная стратегия, разбор типичных ошибок и фреймворк принятия решений.

Next.jsArchitectureMigration
Читать статью

Shopify Hydrogen vs Next.js Commerce: какую архитектуру выбрать для магазина в 2026

Детальное сравнение двух основных headless-архитектур для Shopify. Модель исполнения, слой данных, стратегия кэширования, размер бандла, бенчмарки производительности (LCP/INP/CLS), TCO, риск вендор-локина и практический фреймворк решений между нативной глубиной Hydrogen и компонуемой гибкостью Next.js Commerce.

eCommerceNext.jsShopify
Читать статью