从 Fiber 到异步 React
副标题:现代 React 背后缺失的心智模型
- 原文:From Fiber to Async React
- 作者:Nonso O.
- 发布日期:2026 年 2 月 3 日
原文页头可见文本(保留):
- Latest / Snippets / About
- Clueless Words icon
- Home / react
距离 React 19 发布已有一段时间,此后我们见到了可以加入应用中的新 API、新 Hook 和新组件。有人会说这些变化很受欢迎,也有人觉得这些新增能力很酷,但意义何在?
不妨先从一个问题开始:在 React 里,我们要如何做数据请求,或任何异步操作? 当然,我们可以选用 TanStack Query、SWR 等数据请求/异步库——但假设我们没有这些库,又该怎么办?
下面的代码片段你可能并不陌生:
const UsersList = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUsers = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch("/api/users");
if (!response.ok) {
throw new Error("Failed to fetch users");
}
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchUsers();
}, []);
};
传统上,我们常用 useEffect 在 React 应用里处理异步工作(包括数据请求)。上面这个组件在挂载到 DOM 时调用 fetchUsers,并分别设置 data、loading、error,以便向用户展示合适的 UI。
这种做法能跑起来,但正如社区与 React 核心团队多次指出的:它有不少缺陷,并不是在 React 中处理异步工作的推荐方式。
事实上,React 一直在与异步打交道,即便它并未显式建模。数据请求、代码分割、用户输入、动画、导航都是异步发生的,然而在 React 历史的很长一段时间里,这些关注点都活在渲染系统之外,而不是之内。
于是我们要自己把 loading、effect、命令式更新拼在一起,才能在异步完成时保持 UI 一致。但这本该是应用开发者的责任吗?还是应该由别处承担?
再想一想:如果异步工作活在渲染系统之内,而不是环绕着它,代码库会是什么样子? 它又会如何改变我们构建组件、组件体系乃至整个应用的方式?
React 19 的发布,补全了 「Async React」 这一叙事——本文会逐步拆解这个词。我们会看 Async React 如何解决 UI 与异步工作之间的协同问题,并尝试回答上面的问题。也会触及新问题:在 Async React 已经到来的前提下,库作者应如何构建、组合他们提供给应用开发者的工具?
对开发者来说,组织 React 应用的方式正在进入一个全新阶段,这些变化也能扩展我们能创造的产品形态。全文会有不少深度段落,在阅读现代 React 的构建方式时,可以把下面几点记在心上。
React 协调器(Reconciler)
创建 React Web 应用时,你常会注意到两个重要包:React 与 React-dom。React 让你把 UI 描述为状态的函数,我们常写作 UI = f(state),并用 JSX 来表达。
那 react-dom 扮演什么角色?
react-dom 包含面向 Web 的 React 协调器(reconciler) 与 渲染器(renderer)。协调器负责随时间比较渲染输出、调度工作,并确定要在宿主平台1上应用的最小更新集合;渲染器则把协调器的输出应用到宿主——此处即 DOM。
讨论 Async React 以及新 API 何以可能时,协调器至关重要。在 React Conf 2017 上曾透露:React 的协调器被彻底重写,从所谓的 stack reconciler(栈协调器) 演进为 React Fiber。
栈协调器(Stack Reconciler)
栈协调器是 React 16 之前早期版本使用的协调引擎。它的设计刻意贴近 JavaScript 的函数调用方式,因此直接依赖调用栈来遍历并渲染组件树。
当渲染被触发——通常由状态变化引起——React 会从根节点开始,递归地自上而下遍历组件树,调用每个组件的 render,做 diff,然后提交结果。这意味着一旦协调/渲染开始,React 必须跑完整个过程,才能把控制权交还给浏览器。
(原文含可交互演示:点击 “Start render” 观察 dashboard 中 count 的更新过程;此处为静态译文,完整交互见原文页面。)
该演示中的关键界面文案(按原文保留):
- AppLayout / Dashboard / count: 0 / Sidebar / SidebarItem / Footer
- idle / render / commit
- Browser Main Thread
- Ready
- Responsive to user input
- Start render / Reset Component Tree
这种方式完全同步、不可打断:所有渲染工作在一趟里完成,所有更新优先级相同。系统简单可预测,但也意味着长渲染会阻塞主线程、拖慢输入,在复杂应用里造成明显卡顿。
在栈协调器的局限之下,我们能否想象一种可改进的世界?
随堂测验(原文为交互): 在栈协调器下,React 无法暂停工作、无法重新排定更新优先级、也无法放弃已不再相关的渲染。—— 见原文页面作答。
原文交互项:True / False / Submit
React Fiber
React Fiber 是对 React 协调器的完全重写,在 React 16 引入,用以解决基于栈的方案的限制。Fiber 不再用单次不间断的大渲染,而是建立组件树自己的内部表示——Fiber 树——以及自己的调度模型。
在 Fiber 中,每个组件是一个可独立处理的工作单元(fiber)。渲染被拆成许多小步,React 可以暂停、让出给浏览器2、稍后继续。因此 Fiber 把 协调/渲染阶段 与 提交(commit)阶段 拆成两个独立过程。
批注:原文此处为独立小标题 The Fiber Node(非普通正文句)。
Fiber 节点
Fiber 节点是一个轻量对象,描述与组件相关的元数据。每个 Fiber 含有指向父、子、兄弟的指针。下面是一个极度简化的示例,完整实现见 React 源码:
const fiberNode = {
tag: "",
key: "key",
type: null,
return: null,
child: null,
sibling: null,
ref: null,
pendingProps: null,
memoizedProps: null,
updateQueue: null,
memoisedState: null,
};
每个 Fiber 是一个离散工作单元,React 可以一次处理一个节点,而不必依赖必须跑完的递归调用。单元之间,React 可以判断是继续渲染还是把控制权交回浏览器。若要暂停,React 停止遍历、在 Fiber 树中保留位置并 yield;恢复时从下一个 Fiber 继续,仿佛未曾中断。
这一过程即 time slicing(时间切片),是把渲染工作建模为 JavaScript 对象的直接结果。
在协调/渲染阶段(现称 reconciliation phase),会生成待渲染到 UI 的变更列表,但尚未提交到 DOM;变更被安排在下一阶段提交。提交阶段(commit phase) 才把这些变更落到 DOM。
要点:协调阶段可以被打断、暂停/恢复、完全丢弃;提交阶段则不能打断——一旦开始,必须完成才能做其他事。
随堂测验(原文为交互): Fiber 可以随时间准备多版 UI,但只对 DOM 提交最终变更,从而避免不必要的 DOM 更新。—— 见原文。
原文交互项:True / False / Submit
由于协调阶段可打断,两段独立工作可以都在协调阶段内完成,再以不同顺序进入提交阶段。这让 Fiber 在「何时把渲染更新显示到 DOM」上引入了优先级概念。
Fiber 让 React 先处理高优先级更新、延后低优先级更新,从而改善感知性能与体验。例如:在输入框里打字,比「后台刚加载完的数据」更紧急——那就先保证输入框状态立刻可见,数据渲染可以等用户打完再跟上。这就是所谓更好的感知性能。
Fiber 的优先级大致包括:
- 同步工作(点击/输入等——行为类似栈协调器)
- Task 工作
- 动画(由
requestAnimationFrame()启动) - 高优先级
- 低优先级
- 离屏(当前不可见但预渲染会有帮助的内容)
在讨论 React 18、19 引入的 API、Hook 与组件时,这些优先级组会再次派上用场。核心结论是:没有迁到 Fiber,就不会有 Async React 的概念。
批注:原文边栏/小节标题为 More on the reconciler(以下「更多阅读」即对应该部分的展开)。
更多阅读
想深入了解协调器与 Fiber,可看 Lin Clark 的演讲:A Cartoon Intro to Fiber,或 Brandon 的演讲:Algebraic effects, Fibers, Coroutines Oh my!。
与 Fiber 协调器密切相关的浏览器 API 还有:requestIdleCallback、requestAnimationFrame。
现代 React 特性
Fiber 是工程上的里程碑:可中断渲染、协作式调度、优先级等等。有了这些概念,我们才能想象构建 React 应用的新世界。但 Fiber 发布时缺了让开发者接入这套新范式的 API。
批注:原文在段前使用独立小标题 APIs definition(提示作者将说明「API」一词的两种用法;不应并入上一段当作续写)。
关于「API」一词
文中我宽松地用 API 指 Hook、函数、组件等;而 React 对 API 也有正式定义(指 React 包导出的函数)。下文会区分「广义 API」与「狭义 React API」。
React 18 带来的 API,让开发者终于能用到 Fiber 引入的部分概念。注意我用了部分——直到 React 19(尤其是 19.2),开发者才拿到剩余 API,完整解锁 Fiber 的能力。
例如:
- Suspense
startTransition(API)与useTransitionuseDeferredValueuseOptimisticuseFormStatususeActionStateuse(API)- Activity
- View Transitions(实验性)
这些在 React 文档中有详尽说明,也有不少教程。下面只挑几样与 Fiber 的关系。
Fiber 与 transition 的关系
前面说过,迁到 Fiber 是 transition 存在的前提。用栈协调器可以看一个对比示例(原文含交互:先加低优先级任务再加高优先级,观察何时渲染到屏幕)。
该示例中的界面文案(按原文保留):
- Add Task
- Low priority Task / High priority Task
- Reset
- Pending / Processing / Completed
在栈协调器下,无论优先级如何,项出现在屏幕上的时机没有区别。当混合了本应高/低优先级的更新时,渲染顺序往往遵循后进先出。这带来问题:若许多低优先级更新排在高优先级之后,在栈协调器世界里,高优先级更新要等低优先级全部渲完才能上屏——无法按优先级调度渲染工作。
Fiber 通过优先级解决了这一点:先调度高优先级,再处理低优先级。换 Fiber 后再看同一示例(原文第二个交互),差异很明显:高优先级总是先渲染;若正在渲低优先级时来了高优先级,会暂停低优先级、先完成高优先级,再继续低优先级。
第二个示例同样包含:Add Task / Low priority Task / High priority Task / Reset / Pending / Processing / Completed。
实现这种更新优先级调度的 API 就是 transition,尤其是 startTransition API 与 useTransition Hook:它们把一组更新标为较低优先级,让 React 知道可以延后到高优先级工作完成之后。
Fiber 与 Suspense 的关系
Fiber 带来了可中断渲染,因此 React 可以暂停/恢复渲染,并丢弃尚未提交到 DOM 的工作。
这也深刻影响了 Suspense:在子节点加载完成前显示 fallback UI。当组件在协调阶段 throw 一个 Promise 时,称该组件被 suspend(挂起)。React 不把它当错误,而把 thrown promise 理解为「该组件尚不能完成渲染」。最近的 Suspense 边界会捕获这个 promise,暂停该子树的渲染,转而显示 fallback;直到 promise resolve,子树渲染才继续。
之所以能这样做,是因为 Fiber 允许在协调阶段打断渲染:此时还没有任何 DOM 变更,部分完成的工作可以安全放弃。
(原文含 Suspense 演示:点击 “Render Components”;完整交互见原文。)
点击后 Suspended component 开始渲染,在渲染中 throw 一个 3 秒后 resolve 的 promise;pending 期间 Suspense 显示 fallback(「Loading」);resolve 后继续渲染并显示最终内容。
其中机制在 Lin Clark 的演讲中有更细讲解。要点:没有可中断渲染,就不会有 Suspense。
Async React
至此,我们看了协调器的角色、从栈协调器到 Fiber 的架构转变,以及 Fiber 如何支撑今天的 Suspense、transition、乐观更新与 Actions。
更要强调的是 我们如何走到今天:协调器重写为异步/并发渲染打下了基础,但建在其上的特性是分多个版本逐步出现的。
结果是:这些特性长期被单独文档化、教学与采用。Suspense 被说成加载机制,transition 被说成性能优化,乐观更新用于 UX,Actions 偏向服务端表单——这些说法都不算错,但都忽略了更大的图景。
这些 API 不是彼此割裂的点子,而是 Fiber 所实现的同一套底层模型的不同表达:协同的、带优先级的、异步/并发渲染。
可以逐步采用这些特性,但代码容易变成混合架构,无法充分享受 Async React:代码能跑,心智模型却支离破碎,复杂度上来后越来越难推理。
异步优先(async first)的思维
构建现代 React 应用,需要调整我们看待架构的方式。
异步优先指:用声明式工具表达用户意图、加载状态、优先级与视觉连续性,让 React 把渲染视为可调度、可中断的操作,并由 React 自己协调。
我们与用户之间形成一种契约:用户立刻操作时,UI 立刻响应,异步工作在后台进行;数据就绪且没有更高优先级工作时,再以协同的方式展示完成态。
Ricky Hanlon 在 React Conf 2025 的演讲 Async React 中对这一过程做了建模(Event → busy → Update → loading → Render → done → Commit)。
(原文含类 Instagram 演示:渲染视图、点赞、点踩、归档/取消归档、切换标签等;见原文。)
演示入口文案:Render View。
这个 demo 用乐观状态、transition、Suspense、Activity 等组合实现。你可能注意到切换标签很快;但若先归档再立刻切标签,会乐观地切到归档标签并在标签区域显示 loading;由于归档页渲染尚未就绪,React 仍把体验留在 feed;异步数据就绪后,再完整切换到归档页。
所有这些协同都由 React 处理,我们只定义交互应呈现的样子。demo 里没有用 useEffect 处理任何异步工作。 那异步优先组件怎么写?
一个异步优先组件
在异步优先组件里,我们可以假设组件渲染时已经具备所需数据。
我们知道可用 Suspense 在 loading fallback 与挂起组件之间协调。但若依赖 Suspense 让 React 协同,出错时呢?promise reject 时呢?
这时需要 Error Boundary。Suspense 在 promise 被 throw 时显示 fallback;Error Boundary 在组件 throw 错误 时显示 fallback。协调仍由 React 完成,我们提供 fallback UI 以及可能的重置方式。
Error Boundary 尚未以函数组件一等公民的方式演进,但在 Async React 里它非常关键。可以手写,也可用 react-error-boundary 等库。
这种模型里我们确实在做假设,但假设之中是 React 亲自协调的工作。可以说 「异步」建在 React 渲染系统之内,而不是环绕它。问题从「我如何随时间协调异步?」变成「React 应如何协调?」
实践上可以这样写:
const UsersList = ({ userDataPromise }) => {
const userList = use(userDataPromise);
return (
<div>
{userList.map((user) => (
<p key={user.id}>{user.name}</p>
))}
</div>
);
};
const AsyncFirstDemo = ({ userDataPromise }) => {
return (
<ErrorBoundary fallback={<ErrorFallback />}>
<Suspense fallback={<LoadingFallback />}>
<UsersList userDataPromise={userDataPromise} />
</Suspense>
</ErrorBoundary>
);
};
还记得最前面的 useEffect 请求示例吗?这样是不是简单得多?异步优先组件把 React 的核心理念带到了「数据不能立刻就绪」的世界:我们向 React 描述组件长什么样、如何表现,协调交给 React。
上文刻意没写数据请求逻辑,重点在组件形态。数据可以通过 Server Components、数据请求库、或手动请求再把 promise 往下传等方式获取。
TanStack Query 等库也已拥抱这种模型,提供返回 promise 的 Hook,可直接用于异步优先组件。当然也可以自己写返回 promise 的请求逻辑。
是的,我们反复说 promise,因为协调不是我们在写:无论 pending、fulfilled 还是 rejected,都是 React 在处理。
(原文有「Show more」折叠与更多演示,见原文。)
异步 React 下的组件体系与路由
这是一种真正的范式转变。乍一看像在拿简单性换抽象:为了声明式、异步优先,要引入 Actions、Suspense/Error Boundary、transition、乐观状态等新概念。
自然会问:我们现在就该这样构建应用吗? 简短回答:是,也不完全是——要更细地看。
异步优先的组件库
我们往往会使用组件库/体系:一套可复用、可组合的 UI,封装设计、行为与无障碍。这些库也应能参与异步优先模型。
这里 Action props 很关键:把一个同步/异步函数包在 transition 里再作为 prop 传入,小改动、大影响,例如:
<Button action={saveUserAction}>Save</Button>
通过 action,Button 可以自动反映自身在渲染生命周期中的状态:pending、disabled、默认防重复点击、把错误抛给最近边界等;使用者只需声明按钮做什么。
原生元素也在朝这个方向走,例如 form 可传 action(同步/异步函数)而不仅是 onSubmit,子组件可获得该 transition 的 pending 状态或返回值。异步优先组件库只是同一思想的延伸。
本质上,异步优先组件是 意图驱动(intent-driven):我们声明 UI 该做什么,而不是为每次异步操作重复样板。我们又在抽象层级上迈了一步。
支持 Suspense 的路由
把异步优先组件、异步组件体系与 支持 Suspense 的路由结合,就是在应用层面拥抱异步优先架构。
支持 Suspense 的路由能原生处理可能在加载数据或代码时挂起的组件:自动在 Suspense 边界内渲染路由,在路由就绪前显示约定的 fallback。我们仍要定义页面级 fallback(骨架屏、转圈等),但何时展示由路由配合完成;也可定义错误 fallback。
这也意味着我们选择了基于 React 异步渲染策略的导航与数据方案:导航被包在 transition 里,视为低优先级更新,React 保持当前 UI 可响应、可见,同时准备下一路由。路由组件因数据或代码挂起时,transition 能减少不必要的 loading 闪烁。
(原文含标签页切换演示与社区集成示例,见原文。)
原文该段出现的主要界面文案(按原文保留):
- Community Integrations
- Installed / Explore / Recommended
- React Router V7
- Async Router
- Connected
- Notifications
- Suspense enabled Notifications
点击 Explore 时导航栏可出现 pending,后台准备组件,就绪后再切换。关键是:准备下一路由时,当前 UI 仍可见可交互。若从 Explore 快速切到 Recommended,会取消 Explore 的准备转而准备 Recommended,导航体验更顺滑。
这些机制由各路由的 API 暴露,实现不同,但接入 Suspense 与 transition 的核心逻辑一致。
哪里能找到这类路由?
例如 Next.js App Router 提供支持 Suspense 的路由方案:可在路由 API 里为 Suspense 与 Error Boundary 定义 fallback,挂起组件由框架处理;基于文件路由时,用 loading.tsx、error.tsx 即可。
React Router v7 的 framework/data 模式也支持,但它不是纯 Suspense 驱动路由:导航都包在 transition 里(低优先级),但不会自动给路由树里所有组件包 Suspense——迁入异步优先世界时要留意,仍可手动用 Suspense 包裹路由以显示合适 fallback。
这些拼在一起很妙:异步优先组件 + 异步优先组件库/体系 + 支持 Suspense 的路由,让应用把 async 当作默认。
收尾
Async React——十年磨一剑,它来了。这是了不起的工程成就,也是今天构建 React 应用的巨大心智转变。
一路走来,Async React 并非偶然:协调器从栈模型迁到 Fiber,使我们能在任何更新提交到 DOM 之前调度、打断、排序、放弃渲染;我们也看清了 Fiber 在 Suspense、transition 等机制背后的作用。
于是 React 18/19 的 API 不是孤立技巧,而是 Fiber 所允许的统一心智模型的不同侧面。
那 Async React 到底是什么?
它是一种 以异步为默认 的思维方式与构建方式,而不是把异步当成边缘情形。由 React 协调「何时、如何」更新 UI,而不是让每个组件自己管理依赖。组件可以假设数据已就绪,未就绪就挂起,自动恢复,无需手写 loading 与生命周期编排。我们写更声明式的代码,把数据请求、渲染与用户交互的复杂度交给 React,以响应式、非阻塞的方式协同。
我们通过 transition 显式包裹低优先级更新来扩展这一模型,让紧急交互保持灵敏,非关键 UI 在后台准备。但随之而来的问题是:是否所有状态更新都要包进 transition?
实践中:不。transition 针对非紧急更新;响应输入、打字、指针反馈等应保持同步,界面才够跟手。若全部包进 transition,会模糊界限,拖慢用户期望立即看到的反馈。
就像语义化 HTML,对 transition 的使用也要有语义与自觉,形成「何时用、何时不用」的共同语言。
这也适用于异步优先组件库。文中说 Action props「应当」参与异步优先模型,不是要用 action 取代 库组件暴露的 onClick,而是 action 与 onClick 并存,让使用者选择同步高优先级更新,或带 pending/禁用/错误态的异步低优先级更新。语义也会随实践演化。
最后,支持 Suspense 的路由原生支持在加载数据或代码时可能挂起的组件,在 Suspense 边界内渲染路由并显示 fallback;我们仍定义 loading 与 error UI 长什么样,何时出现由路由与 React 协同。导航包在 transition 里,准备下一屏时保持当前 UI 可见可点,减少突兀跳转。
更妙的是,其中不少思想不限于 React Web:协调器与平台无关,React Native 等渲染器同样可以走向异步优先、跨平台的构建方式。
和同事聊到时我们也在想:如果这些 API 一次性全给齐,会不会更容易讲清 Async React 的故事?我不确定——但这确实是一个很酷的故事;既然能力已经齐备,值得继续讨论 Async React,并在架构新体验时把这些概念放在心上。
留给你的问题:对编写 Hook、组件与工具的库作者来说,Async React 意味着什么?我们期望这些库如何参与异步优先模型?已有库在探索,但仍有许多未解之问。
就写到这里。希望对你有用,下篇再见……Peace!
练习题
嘘!嘿你!对,就是你! 喜欢这篇文章吗?这里有个小练习可以试试 👀
练习(Exercise)
这是一组和本文配套的问题/小游戏,用来帮助你巩固文中概念。玩得开心!
开始练习(Begin Exercise)
本页目录(on this page)
- the react reconciler
- stack reconciler
- react fiber
- modern react features
- fibers importance with transitions
- fibers importance with suspense
- async react
- think async first
- an async first component
- component systems and routers with async react
- async first component libraries
- suspense enabled routers
- wrapping up
- practice problems
- 原文页最后更新:2026-02-03(Last updated February 3rd, 2026)
- 译文整理日期:2026-04-17(见 front matter
processed_at) - 作者:Nonso O.
- 致谢:Thanks for reading!
- 阅读时长:Read Time
- 社交链接:Twitter / GitHub / YouTube
- 互动数据:Like / 4.2K views / 12
原文页尾原始串联文本(保留):
- TwitterGitHubYoutube
校对说明:已补齐原文末尾信息与页面内目录;星号脚注([^fn-host]、[^fn-yield])与「APIs definition」「The Fiber Node」「More on the reconciler」等边注已单独标为批注/脚注,未并入正文。逐段严格对照见同目录 从-fiber-到异步-react.逐段对照.md。