前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版

React 19 新特性全解析与升级避坑指南

首页2025-02-11 10:00:00Front-End
ReactReact 19前端开发HooksTypeScript

React 19 升级最劝退的往往不是新 API,而是升上去之后控制台里那一片报错。useRef() 不传参数过不了类型检查,ref 回调写成箭头函数的隐式返回会被 TypeScript 判错,propTypes 和函数组件的 defaultProps 干脆没了。这些都不是 bug,是 React 团队有意做的取舍。这篇把新特性和破坏性变更放在一起讲,Actions、use、ref 清理函数各自解决什么问题,哪些老写法必须改,升级按什么顺序走最不容易翻车。

在本篇文章中,我们将从浅入深,和大家一起学习以下知识:

  • React 19 的安装方式、JSX Transform 要求和升级前的准备工作
  • Actions 是什么,它把表单里的哪些样板状态接管掉了
  • useActionState、useFormStatus、useOptimistic 三个新 Hook 的分工
  • 全新的 use API 为什么可以写在条件语句里面
  • ref 的两处变化,清理函数和 ref 作为普通 prop
  • 文档元数据、样式表 precedence、资源预加载这几个渲染层能力
  • 错误处理与 hydration 报错的改进
  • Server Components 与 Server Actions 的定位
  • 被移除的 API 清单、TypeScript 变更和一份可以照着做的升级步骤

# 一、升级前先把环境理顺

安装本身没什么可说的,官方推荐锁死版本号,避免依赖树里混进不同的 React 副本。

# npm
npm install --save-exact react@^19.0.0 react-dom@^19.0.0

# yarn
yarn add --save-exact react@^19.0.0 react-dom@^19.0.0
@前端进阶之旅: 代码已经复制到剪贴板

用 TypeScript 的项目要同步更新类型定义,否则新增的 API 在编辑器里全是红线。

npm install --save-exact @types/react@^19.0.0 @types/react-dom@^19.0.0
@前端进阶之旅: 代码已经复制到剪贴板

真正会卡住人的是 JSX Transform。React 19 要求使用新的 JSX Transform,如果项目里还是老的,启动时会看到这样一段警告。

Your app (or one of its dependencies) is using an outdated JSX transform.
Update to the modern JSX transform for faster performance.
@前端进阶之旅: 代码已经复制到剪贴板

大多数项目其实不受影响,Vite、Next.js、CRA 这些脚手架早就默认开了新 Transform。会中招的一般是自己维护 babel 配置的老项目,或者某个很久没更新的第三方组件库。这里有个坑要注意,警告文案里提到的「依赖」不是随口一说,有时候是你自己没问题,是 node_modules 里某个包在用旧的 React.createElement 路径。

# 二、Actions 把表单的样板状态接管掉了

先说结论,Actions 不是一个新函数,而是一套约定。凡是传给 startTransition 或者传给 <form action={...}> 的异步函数,React 都把它当成 Action 来处理,自动帮你管 pending、错误和乐观更新的回滚。

看一下老写法长什么样。

// 传统写法,三个状态全靠手动维护
function UpdateName() {
  const [name, setName] = useState("");
  const [error, setError] = useState(null);
  const [isPending, setIsPending] = useState(false);

  const handleSubmit = async () => {
    setIsPending(true);
    const error = await updateName(name);
    setIsPending(false);

    if (error) {
      setError(error);
      return;
    }
    redirect("/path");
  };

  return (
    <div>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <button onClick={handleSubmit} disabled={isPending}>
        Update
      </button>
      {error && <p>{error}</p>}
    </div>
  );
}
@前端进阶之旅: 代码已经复制到剪贴板

isPending 这个状态最烦人的地方在于,它必须在 await 前后各设一次,中间只要多一个提前 return 的分支,就有可能漏掉复位,按钮永远卡在禁用态。这个我踩过。

换成 React 19 的写法,useTransition 直接给你一个 isPending。

// React 19,pending 状态交给 transition
function UpdateName() {
  const [name, setName] = useState("");
  const [isPending, startTransition] = useTransition();

  const handleSubmit = () => {
    startTransition(async () => {
      const error = await updateName(name);
      if (error) {
        setError(error);
        return;
      }
      redirect("/path");
    });
  };

  return (
    <div>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <button onClick={handleSubmit} disabled={isPending}>
        Update
      </button>
    </div>
  );
}
@前端进阶之旅: 代码已经复制到剪贴板

React 19 允许在 startTransition 里传异步函数,这是和 React 18 最大的区别。React 18 的 transition 只接受同步回调,异步逻辑得自己在外面裹一层状态。

Action 帮你托管的东西一共四类。pending 状态在请求发起时自动置位、结束时自动复位;配合 useOptimistic 可以在等待期间先把界面改掉;请求抛错时会交给最近的 Error Boundary,同时把乐观更新回滚回去;如果是通过 <form action> 提交的,成功之后表单会自动 reset。

# 三、useActionState 与 useFormStatus

useActionState 是 Actions 的常用封装,把「提交函数 + 返回值 + pending」打包成一个 Hook。

import { useActionState } from 'react';

function ChangeName() {
  const [error, submitAction, isPending] = useActionState(
    async (previousState, formData) => {
      const error = await updateName(formData.get("name"));

      if (error) {
        return error; // 返回值会成为下一次的 state
      }

      redirect("/path");
      return null;
    },
    null // 初始 state
  );

  return (
    <form action={submitAction}>
      <input type="text" name="name" />
      <button type="submit" disabled={isPending}>
        {isPending ? '提交中...' : '更新'}
      </button>
      {error && <p style={{ color: 'red' }}>{error}</p>}
    </form>
  );
}
@前端进阶之旅: 代码已经复制到剪贴板

第一个返回值叫 error 只是这个例子里的命名习惯,它的实际含义是「上一次 Action 的返回值」。你返回校验对象、返回成功提示,它就是那个东西。第二个是包装后的 action,直接挂到 <form action> 上;第三个是 pending。

Action 函数的第一个参数是上一次的 state,第二个才是 formData。很多人可能没注意到这一点,写成只接 formData 的单参数函数,结果拿到的是上一次的返回值。

useFormStatus 解决的是另一个问题,设计系统里的提交按钮是个独立组件,它需要知道外层表单在不在提交中,但你又不想为此加一路 props。

import { useFormStatus } from 'react-dom';

function SubmitButton() {
  const { pending, data, method, action } = useFormStatus();

  return (
    <button type="submit" disabled={pending}>
      {pending ? '提交中...' : '提交'}
    </button>
  );
}

function MyForm() {
  return (
    <form action={async () => { /* ... */ }}>
      <input type="text" name="username" />
      {/* 子组件自己去读表单状态,不用父组件透传 */}
      <SubmitButton />
    </form>
  );
}
@前端进阶之旅: 代码已经复制到剪贴板

它从 react-dom 导入而不是 react,因为这是 DOM 特有的能力。另外它只能读到父级 <form> 的状态,在 <form> 本身所在的那个组件里调用是拿不到的,必须放在子组件里。

# 四、useOptimistic 让等待期间界面先动起来

网络慢的时候,点了按钮界面纹丝不动,用户第一反应是再点一次。乐观更新就是为了填这段空白。

fe
  • 一、升级前先把环境理顺
  • 二、Actions 把表单的样板状态接管掉了
  • 三、useActionState 与 useFormStatus
  • 四、useOptimistic 让等待期间界面先动起来
  • 五、全新的 use API
  • 六、ref 的两处变化
  • 七、Context 可以直接当 Provider 用
  • 八、文档元数据与样式表
  • 九、资源预加载 API
  • 十、错误处理与 hydration 报错
  • 十一、Server Components 与 Server Actions
  • 十二、被移除的 API 与 TypeScript 变更
  • 十三、升级步骤
  • 总结
  • 参考

← Next.js 15 新特性完全指南 异步API与缓存策略的全部变化Zustand 极简 React 状态管理使用指南与实战技巧 →