getfromcode

React19 新特性

前端开发
文章摘要

React 19 异步渲染新范式导读 这篇资料聚焦 React 19 最核心的异步数据获取能力,解决的是长期以来前端开发者在组件内做数据请求时的样板代码冗余、状态管理割裂、加载态与错误边界难以统一的问题。过去写一个带加载、错误处理、数据回填的列表组件,至少要维护三个 useState:数据、加载中、错误信息,再配合 useEffect 在挂载时触发请求,还…

已有 198 人浏览 发布 2026-01-12 更新 2026-09-13

React 19 异步渲染新范式导读

这篇资料聚焦 React 19 最核心的异步数据获取能力,解决的是长期以来前端开发者在组件内做数据请求时的样板代码冗余、状态管理割裂、加载态与错误边界难以统一的问题。过去写一个带加载、错误处理、数据回填的列表组件,至少要维护三个 useState:数据、加载中、错误信息,再配合 useEffect 在挂载时触发请求,还要处理竞态条件、组件卸载后的 setState 警告,代码量一大就容易出现状态不同步、Loading 闪烁、请求重复触发等问题。React 19 引入的新 Hook 把数据请求和组件渲染生命周期深度绑定,让异步逻辑的写法更接近同步思维,同时天然支持 Suspense 的加载占位和错误边界的异常兜底,不用再手动维护一堆中间状态。

这份资料适合已经有 React 18 项目开发经验、熟悉 Hooks 基本用法、了解 Suspense 基础概念的前端开发者。如果你平时写业务时经常要处理大量表单、列表、详情页的异步数据加载,或者正在维护老项目里一堆重复的 useEffect 请求逻辑,想知道新版 React 能把这些流程简化到什么程度,这份资料可以直接拿来对照着改自己的现有代码。不建议完全没接触过 React 的新手直接看,至少要先搞懂函数组件、useState、useEffect 的基本用法,以及组件渲染、副作用、状态更新的基本流程,不然会看不懂为什么新 Hook 能“在渲染时阻塞”,也理解不了它和 useEffect 发请求的本质区别。

先从异步数据获取的核心 Hook 入手

建议第一遍先重点看新的 use Hook 相关章节,这是 React 19 异步体系的基础,也是和旧版本写法差异最大的地方。资料里会对比传统 useState + useEffect 的实现方式,一步步拆解为什么旧写法繁琐、容易出问题,再引出新 Hook 的参数、返回值、执行时机,以及它为什么能在 Promise 未完成时暂停组件渲染。这里要重点理解“阻塞渲染”不是真的卡死浏览器主线程,而是 React 内部的渲染调度机制在等待 Promise 结果,期间会显示最近的 Suspense 边界提供的 fallback 内容,这和我们以前手动写 loading 状态的逻辑本质上是两套思维模型。

看这部分的时候不要只看概念,要跟着示例代码自己在本地敲一遍。可以先写一个最传统的用户信息获取组件:用 useState 存 user 数据和 loading,useEffect 里调用接口,然后再用新 Hook 重写同一个组件,对比两份代码的行数、状态数量、错误处理的位置。你会明显发现新写法里不需要再手动维护 loading 状态,不需要写 setLoading(true/false),也不用担心组件卸载后更新状态的警告,因为 React 会自动处理渲染的暂停和恢复。同时要注意观察配合 Suspense 的写法,把数据请求的组件包裹在 Suspense 里之后,加载态的展示逻辑就从组件内部抽离到了边界层,以后要改全局加载样式或者骨架屏,不用再去每个组件里改 state。

配套的 Suspense 与错误边界用法

搞懂基础 Hook 之后,接下来要看它和 Suspense、错误边界的组合用法,这才是这套异步方案能落地到真实项目的关键。单独用新 Hook 只是解决了“在哪里发请求”的问题,而配合 Suspense 才能解决“加载的时候显示什么”,配合错误边界才能解决“请求失败了怎么统一处理”。资料里会讲到不同层级的 Suspense 边界怎么划分,比如页面级的加载和局部组件的加载怎么分别设置 fallback,怎么避免多个请求同时触发时的 Loading 闪烁,怎么用 Suspense List 批量处理多个并行请求的加载状态。这些都是实际业务里天天会碰到的场景,以前要么靠自己封装请求 Hook,要么靠第三方状态管理库来做,现在 React 原生就支持了。

这部分的练习建议从一个简单的列表页开始:先实现一个外层 Suspense 显示页面级骨架屏,里面的列表组件用新 Hook 发请求,然后再给列表里的某个子模块(比如用户头像、统计数据)再加一层独立的 Suspense,让不同模块的加载互不影响。之后再加上错误边界组件,故意写错接口地址,看看错误是怎么被边界捕获、怎么展示自定义错误页面、怎么提供重试按钮的。你可以对比一下,如果用以前的 useEffect + try/catch 写法,要实现同样的分层加载和统一错误捕获,需要多写多少重复代码,每个组件里都要写一遍 try/catch 和错误状态的判断,而新的模式下这些逻辑都可以下沉到边界组件里复用。

从示例到真实项目的迁移路径

资料里的示例都是最小化的演示代码,学完之后要落地到自己的项目里,还需要考虑和现有请求库、状态管理方案的结合。比如你项目里用的是 Axios、React Query 或者 SWR,要搞清楚这些库能不能和新的异步 Hook 配合,怎么把现有的请求方法包装成符合要求的 Promise,怎么处理请求缓存、防抖、节流这些常用能力。不要觉得有了原生 Hook 就一定要换掉所有旧的请求方案,很多时候两者是互补的:原生方案适合简单的、组件级别的数据获取,而成熟的请求库依然在缓存策略、后台刷新、分页这些复杂场景里有优势。

练习的时候可以先从一个非核心的业务页面开始改造,比如个人中心、设置页这种流量不大的页面,先把里面的 useEffect 请求换成新的写法,配上 Suspense 和错误边界,跑通整个流程之后再逐步推广到其他页面。改造过程中要注意记录遇到的问题:比如和路由怎么配合、怎么处理路由参数变化时的重新请求、怎么和表单提交后的刷新逻辑结合。资料里会提到一些常见的坑,比如不要在循环和条件判断里用新 Hook、要注意请求的竞态问题怎么避免、服务端渲染场景下的注意事项,这些都是你迁移过程中大概率会碰到的,提前看一遍可以少走很多弯路。

资料怎么配合日常开发练习

这份资料不是用来从头到尾死记硬背的,最好的用法是当成手边的参考手册,边写代码边查。比如你今天要写一个详情页的数据加载,忘了新 Hook 怎么配合 Suspense 写,就翻到对应的示例章节,照着结构套到自己的组件里,跑通之后再回来补看原理部分。不要光看不写,React 的新特性尤其是渲染机制相关的内容,只看概念很容易觉得自己懂了,一写代码就发现各种不符合预期的地方,比如为什么有时候 Loading 不显示、为什么请求发了两次、为什么状态不更新,这些问题只有实际写过才会碰到,也只有碰到了再回去查资料,才能真正理解设计思路。

你还可以基于资料里的示例做扩展练习,比如给基础的用户信息组件加上重新获取的按钮,加上轮询功能,加上参数变化时自动重新请求的逻辑,看看用新的写法怎么实现这些功能,和旧写法比代码量差多少、可维护性差多少。也可以尝试封装一个自己的通用请求 Hook,把新的原生 Hook 包一层,加上统一的错误提示、请求拦截、响应处理,以后在项目里所有的数据获取都用这个封装好的 Hook,既能享受到新 API 的简洁,又能保留项目里统一的请求规范。等你把这些场景都练熟了,再回头看 React 19 的异步设计,就会明白它不是简单加了一个 Hook,而是整个组件数据获取范式的升级。


bb13515d-eb01-4c99-82e2-b6bc11a8dbef.png

use

1.异步数据获取

通过use我们可以在组件 render 执行时进行数据获取。使用use时,它接受一个 Promise 作为参数,会在 Promise 状态为非 fullfilled 时阻塞组件 render。通常我们会使用use配合Suspense来一起使用,从而处理在数据获取时的页面加载状态展示。以往在use出现之前,我们需要在组件中进行数据获取通常需要经历以下步骤:

  • 第一先创建 useState 用于存储获取后的数据以及控制 Loading 加载状态。
  • 第二是初始化时在 useEffect 中进行异步数据获取。
  • 第三最后在数据获取返回后调用 setState 更新数据和 UI 展示。
示例:
import { useState, useEffect } from 'react';
function getPerson(): Promise<{ name: string }> {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ name: 'react19之前的写法' });
}, 1000);
});
}
const personPromise = getPerson();
function App() {
// 使用 useState 控制 UI 展示和数据存储
const [loading, setLoading] = useState(false);
const [name, setName] = useState<string>('');
// useEffect 中进行数据获取
useEffect(() => {
setLoading(true);
personPromise.then(({ name }) => {
// 数据获取成功后调用 setState 更新页面展示
setName(name);
setLoading(false);
});
}, []);
return (
<div>
<p>Hello:</p>
{loading ? 'loading' : <div>userName: {name}</div>}
</div>
);
}
export default App;

在 React 19 新增的use这个 Api 后,我们可以使用 use 配合 Suspense 来简化这一过程:

import { use, Suspense } from 'react';
function getPerson(): Promise<{ name: string }> {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ name: 'react19之后' });
}, 1000);
});
}
const personPromise = getPerson();
function Person() {
// use Api 接受传入一个 Promise 作为参数
const person = use(personPromise);
return <div>userName: {person.name}</div>;
}
function App() {
return (
<div>
<p>Hello:</p>
{/* 同时配合 Suspense 实现使用 use 组件的渲染加载态 */}
<Suspense fallback={<div>Loading...</div>}>
<Person />
</Suspense>
</div>
);
}
export default App;

有条件的读取 React Context( useContext => use(context) )


再来看看 use Api 的另一个用途:有条件的读取 React Context。在 React 19 之前要使用 Context ,只能通过 useContenxt 来使用。由于 React Hook 的特殊性,hook 是无法出现在条件判断语句中。无论之后的条件中是否用得到这部分数据,我们都需要将 useContext 声明在整个组件最顶端。

但在 React19 之后,我们可以通过useapi 来有条件的获取 Context 而不必再局限于传统 hook 的一些限制。

import { use } from 'react';
import ThemeContext from './ThemeContext';
function Heading({ children }) {
if (children == null) {
return null;
}
// 使用 use APi 有条件的获取 Context
const theme = use(ThemeContext);
return <h1 style={{ color: theme.color }}>{children}</h1>;
}

useTransition的改动

在 React19 版本之前,我们需要通过一系列的 hook 来手动处理待处理状态、错误、乐观更新和顺序请求等等状态。

比如一个常见提交表单的用例:

import { useState } from 'react';
import {Button,Input} from "@douyinfe/semi-ui"
function UpdateName() {
const [name, setName] = useState<string>('');
const [error, setError] = useState<any>(null);
const [isPending, setIsPending] = useState(false);
const handleSubmit = async () => {
setIsPending(true);
const error = await updateName(name);
setIsPending(false);
if (error) {
setError(error);
return;
}
console.log('表单更新完毕')
};
return (
<div>
<Input value={name} onChange={(event) => setName(event.target.value)} />
<Button onClick={handleSubmit} disabled={isPending}>
Update
</Button>
{error && <p>{error}</p>}
</div>
);
}

而在 React19 中,对于 useTransition 提供了异步函数的支持,从而可以使用 useTransition 更加便捷的进行异步的数据处理:

import { useState, useTransition } from 'react';
import {Button,Input} from "@douyinfe/semi-ui"
function updateName(name) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(undefined);
}, 1000);
});
}
export default function UpdateName() {
const [name, setName] = useState('');
const [error, setError] = useState(null);
const [isPending, startTransition] = useTransition();
const handleSubmit = () => {
// startTransition 中的异步函数被称为 Action
// 当 startTransition 被调用时 React 会自动变更 isPending 为 true
// 同理,当函数执行完毕后 isPending 会自动变更为 false
startTransition(async () => {
const error = await updateName(name);
if (error) {
setError(error);
return;
}
console.log('表单更新完毕')
});
};
return (
<div>
<Input value={name} onChange={(event) => setName(event.target.value)} />
<Button onClick={handleSubmit} disabled={isPending}>
Update
</Button>
{error && <p>{error}</p>}
</div>
);
}


可以看到在 useTransition 返回的 startTransition 函数中,异步的 startTransition 在点击 update 时会将 isPending 状态自动设置为 true 同时发起异步更新请求。

在 updateName 异步更新请求完成后,React 会自动将 isPending 重置为 false 从而自动控制 button 的禁用状态。

useActionState

在 React19 中,对于表单提交行为的 Action React 提供了更加便捷的方式:

import { useActionState } from 'react';
function updateName(name) {
return new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.5 ? resolve("表单更新完成":name) : reject();
}, 200);
});
}
export default function ChangeName() {
const [state, submitAction, isPending] = useActionState(
async (previousState, formData) => {
//previousState:是上一次的值
try {
const result = await updateName(formData.get('name'));
return result;
} catch (e) {
return e;
}
},
null
);
return (
<form action={submitAction}>
<input type="text" name="name" />
<button type="submit" disabled={isPending}>
Update
</button>
<p>{state}</p>
</form>
);
}


useActionState接受一个函数(“Action”),同时返回被包装好的 Action 方法(submitAction)。

当调用被包装好的 submitAction 方法时,useActionState返回的第三个 isPending 用于控制当前是否为 isPending (被执行状态),同时在 Action 执行完毕后 useActionState 会自动将 Action 的返回值更新到 state 中。


useFormStatus

在 react-dom 库中提供了一个全新的 HookuseFormStatus可以帮助我们更好地控制创建的表单,用于在表单内部的元素来获取到表单当前状态:

import { useFormStatus } from "react-dom";
function Submit() {
const { pending, data, method, action } = useFormStatus();
return (
<button disabled={pending}>
{pending ? "正在提交..." : "提交完成"}
</button>
);
}
const formAction = async () => {
// 模拟延迟 3 秒
await new Promise((resolve) => setTimeout(resolve, 3000));
};
const FormStatus = () => {
return (
<form action={formAction}>
<Submit />
</form>
);
};
export default FormStatus;


  • pending:如果表单处于待处理状态,则为 true,否则为 false。
  • data:一个实现了 FormData 接口的对象,其中包含表单提交的数据。
  • method:HTTP 方法 – GET或 POST
  • action:一个函数引用。


useOptimistic

React 19 引入了useOptimistic来管理乐观更新。

所谓 Optimistic updates(乐观更新) 是一种更新应用程序中数据的策略,这种策略通常会理解先修改前端页面,然后再向服务器发起请求。

  • 当请求成功后,则结束操作。
  • 当请求失败后,则会将页面 UI 回归到更新前的状态。

useOptimistic的主要目的是允许我们假设异步操作成功,并在等待实际结果时相应地更新状态。这种做法的好处是可以防止新旧数据之间的跳转或闪烁,提供更快的用户体验。

比如,在绝大多数提交表单的场景中。通常在某个 input 输入完毕后,我们需要将 input 的值输入提交到后台服务中保存后再来更新页面 UI ,这种情况就可以使用 useOptimistic 来进行所谓的“乐观更新”。

import { useOptimistic, useRef } from "react";
export async function isMessage(message) {
await new Promise((resolve, reject) =>
setTimeout(() => {
if (Math.random() > 0.5) {
resolve();
} else {
reject();
}
}, 1000)
);
return message;
}
export function Thread({ messages, sendMessage }) {
const formRef = useRef();
async function formAction(formData) {
addOptimisticMessage(formData.get("message"));
formRef.current.reset();
await sendMessage(formData);
}
//入参一:初始状态和未进行任何操作时返回的状态。
//入参二:一个纯函数,它获取当前状态和addOptimistic传递的乐观值,返回合并后的乐观状态。
//optimisticState:乐观状态。如果没有正在进行的操作,则等于状态;否则,它等于updateFn的结果。
//addOptimistic:用于触发乐观更新的函数,接受任何类型的 optimisticValue 并将其传递给 updateFn。
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(state, newMessage) => [
...state,
{
text: newMessage,
sending: true,
},
]
);
console.log(optimisticMessages, "1");
return (
<>
{optimisticMessages.map((message, index) => (
<div key={index}>
{message.text}
{!!message.sending && <small> (Sending...)</small>}
</div>
))}
<form action={formAction} ref={formRef}>
<input type="text" name="message" placeholder="Hello!" />
<button type="submit">Send</button>
</form>
</>
);
}
import { Thread, isMessage } from "./Thard";
import { useState } from "react";
function App() {
const [messages, setMessages] = useState([
{ text: "Hello there!", sending: false, key: 1 },
]);
async function sendMessage(formData) {
try {
const sentMessage = await isMessage(formData.get("message"));
setMessages((messages) => [...messages, { text: sentMessage }]);
} catch (e) {
console.error(e);
}
}
return <Thread messages={messages} sendMessage={sendMessage} />;
}
export default App;


上边的例子中我们使用 useOptimistic 来每次表单提交发送数据前调用addOptimisticMessage将页面立即更新。

isMessage使用Math.random()和延时器模拟了一个请求的成功或者失败,之后等待isMessage异步方法完成后,useOptimistic会根据异步方法是否正常执行完毕从而进行是否保留 useOptimistic 乐观更新后的值。

  • 当 sendMessage Promise Resolved 后,useOptimistic 会更新父组件中的 state 保留之前乐观更新的值
  • 当 sendMessage Promise Rejected 后,useOptimistic 并不会更新 App 中的 state 自然也会重置页面中的值

useOptimistic的应用范围也很广,例如表单提交、点赞、书签、删除以及其他需要立即反馈的场景。



forwardRef的改进

从 React 19 开始,现在可以将 ref 通过 props 在父子组件中进行传递,这能简化代码,forwardRef也成了一个即将要被废弃的 API。(但是如果要通过ref实现调用子组件中的方法的话,仍然需要useImperativeHandle这个hooks将方法暴露出去)


import React, { forwardRef } from 'react';
import {Button} from '@douyinfe/semi-ui'
const ExampleButton = forwardRef((props, ref) => (
<Button ref={ref}>
{props.children}
</Button>
));
之后的写法
import React from 'react';
import {Button} from '@douyinfe/semi-ui'
const ExampleButton = ({ ref, children }) => (
<Button ref={ref}>
{children}
</Button>
);

Context简化

const ThemeContext = createContext('');
function App({children}) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}

refs的优化

import { useRef } from 'react';
function ExampleComponent() {
const myRef = useRef(null);
function handleRef(node) {
// 在创建ref/卸载DOM时调用
if (node) {
// 执行你的操作,比如操作 DOM
console.log(node);
} else {
// 在清除 ref 时调用,此时 node 是 null
console.log('Ref is cleared');
}
}
return (
<div ref={handleRef}>
{/* ... */}
</div>
);
}


React 编译器

React 编译器是一个**「自动记忆编译器」,可以自动执行应用程序中的所有记忆操作。react19之前的版本,当状态发生变化时,React有时会重新渲染不相干的部分,我们针对此类情况的解决方案一直是「手动记忆化」,**应用useMemouseCallbackmemoAPI来手动调整React在状态变化时重新渲染的部分。但手动记忆化可能会使代码变得复杂、容易出错、并需要额外的工作来保持更新。React 团队意识到手动优化很繁琐。React 团队创建了React 编译器。React 编译器现在将管理这些重新渲染。有了这个功能,我们不再需要手动处理这个问题。目前React Compiler 仍然处于 experimental 状态。

兼容 Web Components

Web 组件允许我们使用原生HTMLCSSJavaScript创建自定义组件,无缝地将它们整合到我们的 Web 应用程序中,就像使用HTML标签一样。

之前在react中使用 web 组件的方式:

class MyButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
const button = document.createElement('button');
button.textContent = this.getAttribute('label') || 'Click me';
button.addEventListener('click', () => {
const event = new CustomEvent('button-click', {
detail: { message: 'Button clicked!' }
});
this.dispatchEvent(event);
});
this.shadowRoot.append(button);
}
}
customElements.define('my-button', MyButton);
// MyButton.js
import React, { useRef, useEffect } from 'react';
const MyButton = ({ label, onClick }) => {
const buttonRef = useRef();
useEffect(() => {
const buttonElement = buttonRef.current;
const handleButtonClick = (e) => {
if (onClick) {
onClick(e);
}
};
//需要手动绑定事件
buttonElement.addEventListener('button-click', handleButtonClick);
// 清除事件监听器避免内存泄漏
return () => {
buttonElement.removeEventListener('button-click', handleButtonClick);
};
}, [onClick]);
return <my-button ref={buttonRef} label={label}></my-button>;
};
export default MyButton;