~/ ?.log $
返回文章列表
12 min read
更新于 2026年3月6日

我不知道的 React(14)— React 19 核心特性:Compiler、Actions 与新 Hooks

React 19 于 2024 年底正式发布。这个版本带来的变化不是"多了几个新 API"那么简单——它在编译层面、数据交互层面和异步处理层面同时发力,试图从根本上减少 React 开发中最常见的两类样板代码:手动性能优化和手动异步状态管理。

React 19 于 2024 年底正式发布。这个版本带来的变化不是”多了几个新 API”那么简单——它在编译层面、数据交互层面和异步处理层面同时发力,试图从根本上减少 React 开发中最常见的两类样板代码:手动性能优化和手动异步状态管理。

很多人看到 React Compiler 就兴奋地以为”再也不用写 useMemo 了”,看到 Actions 就以为”表单提交不用管 loading 状态了”。思路是对的,但 Compiler 有它的前提条件,Actions 有它的适用边界。这篇把 React 19 的核心特性拆开讲清楚——它到底做了什么,以及它做不到什么。

一、React Compiler:编译时的自动 Memoization

在 React 19 之前,避免不必要的重渲染需要开发者手动用 useMemo 缓存计算结果、用 useCallback 缓存函数引用、用 React.memo 缓存组件。这些优化手段本身不复杂,但有两个实际问题:一是容易遗漏(团队里有人忘了加 React.memo,整条优化链就断了),二是加多了反而影响可读性(到处都是 useMemo 包裹的代码,业务逻辑被稀释了)。

React Compiler 的做法是:在编译阶段(构建时)对组件代码做静态分析,自动识别哪些值可以跨渲染复用,然后在底层插入 memoization 逻辑。 开发者不需要手动写 useMemouseCallback,Compiler 自己判断哪里需要缓存。

但这里有一个很多人会忽略的前提——Compiler 的分析依赖一个假设:你的组件是纯函数,state 和 props 是不可变的。

如果组件里有这类代码,Compiler 可能无法正确优化,甚至直接跳过:

function Counter({ items }) {
  items.sort((a, b) => a - b);
  return (
    <ul>
      {items.map((item) => (
        <li key={item}>{item}</li>
      ))}
    </ul>
  );
}

items.sort() 直接修改了传入的 props 数组——这违反了”props 不可变”的规则。Compiler 如果假设 items 在两次渲染之间引用没变就可以跳过渲染,但实际上 items 的内容已经被 sort 改了。这种情况下,要么 Compiler 保守地跳过优化,要么产出错误的结果。

正确的写法是先拷贝再排序:

function Counter({ items }) {
  const sorted = [...items].sort((a, b) => a - b);
  return (
    <ul>
      {sorted.map((item) => (
        <li key={item}>{item}</li>
      ))}
    </ul>
  );
}

说白了,React Compiler 不是”帮你优化烂代码”,而是”帮你把符合规范的代码自动优化到最佳”。写出符合 React 规则的代码是前提,Compiler 只是把手动加 useMemo/useCallback 这步给自动化了。

另外,Compiler 解决的是”因状态变化导致的不必要重渲染”问题。它不能解决所有性能问题。 大列表渲染(需要虚拟化)、动画性能(需要 GPU 加速)、网络请求优化——这些不在 Compiler 的能力范围内。

二、Actions:表单提交的标准化方案

在 React 19 之前,一个典型的表单提交处理长这样:

function CreateUserForm() {
  const [isPending, setIsPending] = useState(false);
  const [error, setError] = useState(null);
  const [success, setSuccess] = useState(false);

  async function handleSubmit(e) {
    e.preventDefault();
    setIsPending(true);
    setError(null);
    try {
      await createUser(new FormData(e.target));
      setSuccess(true);
    } catch (err) {
      setError(err.message);
    } finally {
      setIsPending(false);
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" required />
      <button disabled={isPending}>{isPending ? '创建中...' : '创建用户'}</button>
      {error && <p style={{ color: 'red' }}>{error}</p>}
      {success && <p style={{ color: 'green' }}>创建成功</p>}
    </form>
  );
}

三个 useStatetry/catch/finally 里手动切换状态——每写一个表单都是这套模式。代码不难写,但重复度很高,而且容易漏掉某个状态的重置。

React 19 的 Actions API 把这个模式标准化了。核心变化是:<form> 标签的 action prop 可以直接接受一个异步函数,React 自动管理这个函数执行期间的 pending 状态。

import { useFormState, useFormStatus } from 'react-dom';

async function createUserAction(prevState, formData) {
  const name = formData.get('name');
  try {
    await createUser(name);
    return { success: true, message: `用户 ${name} 创建成功` };
  } catch (err) {
    return { success: false, message: err.message };
  }
}

function CreateUserForm() {
  const [state, formAction] = useFormState(createUserAction, {
    success: false,
    message: null,
  });

  return (
    <form action={formAction}>
      <input name="name" required />
      <SubmitButton />
      {state.message && <p style={{ color: state.success ? 'green' : 'red' }}>{state.message}</p>}
    </form>
  );
}

function SubmitButton() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? '创建中...' : '创建用户'}
    </button>
  );
}

两个关键点。useFormState 接收一个 Action 函数和初始状态,返回当前状态和一个包装后的 formAction——把 formAction 传给 <form action>,Action 的返回值会自动成为新的 state。useFormStatus 返回最近父级 <form> 的提交状态,pending 字段在 Action 执行期间为 true

这两个 Hook 的分工和 ErrorBoundary 的两个方法有些相似(还记得第 11 篇吗?):useFormState 负责”结果管理”,useFormStatus 负责”过程反馈”。一个管”最终变成了什么”,一个管”现在正在做什么”。

换句话说,Actions API 不是新发明了一种表单处理模式,而是把大家本来就在写的 isPending + try/catch 模式做了标准化封装,减少了手动管理状态的代码量。

三、useOptimistic:乐观更新的内建支持

很多人以为网络请求的用户体验问题只有”加载慢”一种,但还有一种更微妙的问题——用户操作后必须等服务器确认才能看到结果。 比如点了”点赞”按钮,要等 API 返回成功后数字才加 1。网络快的时候感觉不到延迟,网络稍慢就会觉得”点了没反应”。

乐观更新的策略是:先假设操作会成功,立刻更新 UI,同时在后台发送请求。如果请求成功,一切如常;如果失败,回滚到之前的状态。

React 19 之前,乐观更新需要手动实现——用一个临时 state 显示乐观值,请求完成后切换回真实值,失败时回滚。useOptimistic 把这个流程内建了:

function LikeButton({ initialCount, onLike }) {
  const [optimisticCount, setOptimisticCount] = useOptimistic(
    initialCount,
    (current, increment) => current + increment,
  );

  async function handleLike() {
    setOptimisticCount(1);
    try {
      await onLike();
    } catch {
      // 请求失败时 React 自动回滚到 initialCount
    }
  }

  return <button onClick={handleLike}>赞 {optimisticCount}</button>;
}

setOptimisticCount(1) 调用后,optimisticCount 立刻从 initialCount 变成 initialCount + 1(由第二个参数——更新函数决定)。如果异步操作成功且外部传入的 initialCount prop 更新了,React 会把乐观值和真实值合并。如果异步操作失败,optimisticCount 自动回退到 initialCount

四、use Hook:在渲染中读取 Promise 和 Context

use 是 React 19 新增的一个特殊 Hook——说它”特殊”是因为它打破了 Hooks 的常规规则:use 可以在条件语句和循环中调用。

它有两个用途。

读取 Context,功能上等价于 useContext,但语法更简洁,而且可以在 if 块中使用:

function Button() {
  const theme = use(ThemeContext);
  return <button className={theme}>按钮</button>;
}

读取 Promise,配合 Suspense 使用。当 use 接收一个 Promise 时,如果 Promise 还没 resolve,组件会挂起(suspend),触发最近的 Suspense 边界显示 fallback:

function MessageComponent({ messagePromise }) {
  const message = use(messagePromise);
  return <p>{message}</p>;
}

function App() {
  return (
    <Suspense fallback={<p>加载中...</p>}>
      <MessageComponent messagePromise={fetchMessage()} />
    </Suspense>
  );
}

问题的关键在于——use 读取 Promise 的前提是 Promise 已经被创建且传给了组件。不要在组件内部每次渲染都创建新的 Promise,否则会陷入无限挂起。Promise 应该在父组件中创建,或者通过状态管理库缓存。

五、其他语法层面的简化

React 19 还做了两个小但很实用的简化。

ref 不再需要 forwardRef。 函数组件现在可以直接接收 ref 作为 prop:

// React 19 之前
const Input = forwardRef((props, ref) => <input ref={ref} {...props} />);

// React 19
function Input({ ref, ...props }) {
  return <input ref={ref} {...props} />;
}

少写一层 forwardRef 包裹,代码更直观。

Context.Provider 不再必须。 Context 对象本身可以直接作为 Provider 使用:

// React 19 之前
<ThemeContext.Provider value="dark">
  <App />
</ThemeContext.Provider>

// React 19
<ThemeContext value="dark">
  <App />
</ThemeContext>

这两个改动都是纯粹的语法简化,底层行为没有变化。

六、总结

React 19 的核心思路是”把开发者本来就在做的事情自动化”。Compiler 自动做 memoization(你不用写 useMemo/useCallback 了),Actions 自动管理表单提交的 pending/error/success 状态(你不用写三个 useState 了),useOptimistic 自动处理乐观更新的回滚(你不用手动维护临时状态了)。

但这些自动化都有前提条件——Compiler 要求代码是纯函数且遵循不可变性,Actions 只标准化了”请求-响应”模式的表单交互,useOptimistic 解决的是 UI 反馈延迟而不是网络延迟本身。理解它们的能力边界,才能在合适的场景使用合适的工具。


本系列其他文章:

相关主题:

share.ts

// 觉得这篇文章有帮助?

// 欢迎分享给更多人

export const  subscribe  =  "/rss.xml" ;