React 19 实战:并发特性、Actions 与新 Hooks

前端工程

React 19 为什么值得升级

React 19 把过去几年实验性的并发特性正式收敛为稳定 API,并补齐了「表单 / 数据变更」这一长期短板。核心变化可以概括为一句话:用 Action 包裹「写操作」,用并发原语包裹「读操作」,让 UI 在等待中不卡顿

能力 React 18 React 19
Server Components 实验(Next.js 等框架) 成为文档级稳定特性
表单提交 手写 useState + onSubmit useActionState / <form action>
读取 Promise / Context use 不适用 use() 可在渲染中读 Promise/Context
ref 只能 useRef + forwardRef 可直接作为 Props 传递
乐观更新 手写状态切换 useOptimistic
并发过渡 useTransition useTransition + useDeferredValue 增强

Actions:把「写操作」变成一等公民

Action 是一个「可以被 React 等待」的函数。配合 <form action>useActionState,React 会自动管理 pending 状态并禁用提交按钮。

"use client";
import { useActionState } from "react";

async function subscribe(prevState: { ok: boolean; msg: string }, formData: FormData) {
  const email = formData.get("email");
  // 这里放真正的服务端调用(Server Action 或 fetch)
  const res = await fetch("/api/subscribe", {
    method: "POST",
    body: JSON.stringify({ email }),
  });
  if (!res.ok) return { ok: false, msg: "订阅失败,请重试" };
  return { ok: true, msg: "订阅成功!" };
}

export function SubscribeForm() {
  const [state, formAction, pending] = useActionState(subscribe, {
    ok: false,
    msg: "",
  });

  return (
    <form action={formAction}>
      <input name="email" type="email" required />
      <button disabled={pending}>{pending ? "提交中…" : "订阅"}</button>
      <p>{state.msg}</p>
    </form>
  );
}

useFormStatus 则让它能在子组件里拿到父表单的状态,无需层层透传:

import { useFormStatus } from "react-dom";

function SubmitButton() {
  const { pending } = useFormStatus();
  return <button disabled={pending}>{pending ? "保存中…" : "保存"}</button>;
}

配置类数据(如接口返回的表单默认值)可先用 JSON 格式化 工具整理,再回填到 initialState,避免手写大对象出错。


use:在渲染中读 Promise 与 Context

use() 是 React 19 引入的「渲染期读取」原语:它能直接 await 一个 Promise,也能读 Context,并且会在 Suspense 边界处挂起。

import { use } from "react";

function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
  const comments = use(commentsPromise); // 挂起直到 resolve
  return (
    <ul>
      {comments.map((c) => (
        <li key={c.id}>{c.body}</li>
      ))}
    </ul>
  );
}

注意:use() 必须在组件或 Hook 的渲染阶段调用,不能放在 try/catch 之外的条件分支里——它不是一个普通函数,而是告诉 React「这里可能要等」。


并发渲染:让等待不卡 UI

useTransition:把「慢更新」标记为非紧急

import { useTransition, useState } from "react";

function Search() {
  const [query, setQuery] = useState("");
  const [list, setList] = useState<Item[]>([]);
  const [isPending, startTransition] = useTransition();

  function onChange(e: React.ChangeEvent<HTMLInputElement>) {
    const value = e.target.value;
    setQuery(value); // 输入框立刻响应
    startTransition(() => {
      setList(filterExpensive(value)); // 重计算包在 transition 里
    });
  }

  return (
    <div>
      <input value={query} onChange={onChange} />
      <ul style={{ opacity: isPending ? 0.6 : 1 }}>
        {list.map((i) => (
          <li key={i.id}>{i.name}</li>
        ))}
      </ul>
    </div>
  );
}

useDeferredValue:自动「延迟」一个值

当输入与渲染解耦时,useDeferredValue 更省心:

const deferred = useDeferredValue(query);
const results = useMemo(() => filterExpensive(deferred), [deferred]);

两者的区别:useTransition 包裹「一段状态更新」,useDeferredValue 派生「一个被延迟的值」,通常和 useMemo 配合。


useOptimistic:乐观更新开箱即用

import { useOptimistic, useRef } from "react";

type Msg = { id: number; text: string; sending?: boolean };

function Chat() {
  const [messages, setMessages] = useOptimistic<Msg[]>([]);
  const input = useRef<HTMLInputElement>(null);

  async function send(formData: FormData) {
    const text = formData.get("text") as string;
    // 立即乐观插入
    setMessages((list) => [...list, { id: Date.now(), text, sending: true }]);
    await fetch("/api/chat", { method: "POST", body: JSON.stringify({ text }) });
  }

  return (
    <form action={send}>
      <input ref={input} name="text" />
      {messages.map((m) => (
        <p key={m.id}>{m.text}{m.sending ? "(发送中)" : ""}</p>
      ))}
    </form>
  );
}

涉及主题色、按钮对比度时,可借助 颜色转换颜色对比度 工具校验 UI 可访问性(WCAG)。


ref 作为 Props 与文档元数据

ref 不再是特例

function MyInput({ ref }: { ref: React.Ref<HTMLInputElement> }) {
  return <input ref={ref} />;
}
// 使用:<MyInput ref={myRef} />  —— 不再需要 forwardRef

文档元数据原生支持

import { Title, Meta } from "react-dom";

function Page() {
  return (
    <>
      <Title>页面标题</Title>
      <Meta name="description" content="页面描述" />
    </>
  );
}

从 React 18 平滑迁移

  1. 升级依赖npm i react@19 react-dom@19,并升级 @types/react
  2. 替换 forwardRef:直接把 ref 写进 Props 即可,旧写法在 19 中已被弃用但暂兼容。
  3. 落地 Actions:把「提交后 setState + loading」的表单迁移到 useActionState
  4. use 收口异步:Server Components 下把数据 Promise 传给子组件,用 use() 读取。
  5. 接入并发:列表/搜索类交互用 useDeferredValueuseTransition 提升响应感。

常见问题 FAQ

Q1:Actions 只能在 Server Component 里用吗?

不是。客户端也能用 useActionState 包裹普通异步函数;Server Action 只是其中一种「函数直接跑在服务端」的形式。

Q2:use() 和 await 有什么区别?

await 是 JS 运行时语义,use() 是 React 的渲染语义:它会在 Suspensive 边界挂起,并能被并发调度打断重渲染。

Q3:useTransition 和 useDeferredValue 选哪个?

要「延迟一整段更新」用 useTransition;要「基于某个快速变化的值派生慢结果」用 useDeferredValue + useMemo

Q4:React 19 还支持 IE 吗?

不支持,且已彻底移除旧版遗留行为。现代浏览器环境是前提。

Q5:乐观更新失败怎么回滚?

useOptimistic 会在传入的 Action 抛错时自动回滚到真实状态;你只需保证 setMessages 的「真实数据源」在失败时不变即可。


工具推荐

在 React 19 开发中,以下 工具库 能帮上忙:


React 19 的精髓不是「又多了几个 Hook」,而是把「写操作(Action)」与「读操作(并发/use)」用统一心智模型管起来:UI 在等待数据时不卡顿,在提交变更时状态自管。掌握 Action + 并发原语,你就握住了现代 React 的钥匙。

本站提供浏览器本地工具,免注册即可试用 →

#React#React 19#并发渲染#前端#useTransition#Server Components#Actions