React 18
Suspense
可以“等待”目标 UI 加载,并且可以直接指定一个加载的界面(像是个 spinner),让它在用户等待的时候显示。
<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>虽然 Suspense 在 React 16 中引入,主要用于代码拆分,并且与 React.lazy 已经存在了一段时间,但 React 18 引入了新的能力,使得 Suspense 能够处理数据获取。
使用 Suspense,我们可以延迟组件的渲染直到满足特定条件,例如从远程源加载数据。同时,我们可以渲染一个占位组件来提示该组件仍在加载中。
Suspense 真正的威力来自于它与 React 并发特性的深度集成。当一个组件被暂停(例如因为它仍在等待数据加载),React 不会空闲地等待组件接收数据。相反,它暂停了被挂起组件的渲染,并将重点转向其他任务。
并发渲染
React 的视觉更新分为两个阶段:渲染阶段和提交阶段。React 的渲染阶段是一个纯计算阶段,在这个阶段中,React 元素与现有的 DOM 进行对比。这个阶段涉及创建一个新的 React 元素树,也被称为 “虚拟 DOM”,它实际上是实际 DOM 的轻量级内存表示。
在渲染阶段,React 计算当前 DOM 与新的 React 组件树之间的差异,并准备必要的更新。

在渲染阶段之后是提交阶段。在这个阶段,React 将在渲染阶段计算出的更新应用到实际的 DOM 上。这包括创建、更新和删除 DOM 节点,最终渲染出新的 React 组件树。
在传统渲染中,直到完成整个树的渲染任务,并将结果提交到 DOM 中,实现屏幕上组件的视觉更新。整个过程是不可中断的。
也就是说,用户在 React 完成渲染并提交结果到 DOM 之前,尝试与应用程序进行交互时,会遇到无响应的用户界面。
React 18 引入了一种全新的并发渲染,它在后台运行。这个渲染为我们提供了一些方式来标记某些渲染为非紧急任务。

在渲染低优先级组件(粉色)时,React 会让出主线程,以便检查是否有重要的任务。
在这种情况下,React 将每 5ms 让出主线程,以检查是否有更重要的任务需要处理,例如用户输入,甚至是渲染另一个在当前对用户体验更重要的 React 组件。通过不断地让出主线程,React 可以使这样的渲染变得非阻塞,并优先处理更重要的任务。

与每次渲染的单个不可中断任务不同,并发渲染器在低优先级组件的(重新)渲染过程中以 5ms 的间隔将控制权交还给主线程。
此外,并发渲染器能够在后台 “并发” 地渲染多个版本的组件树,而不会立即提交结果。
相较于同步渲染是一个全有或全无的计算过程, 并发渲染允许 React 暂停和恢复一个或多个组件树的渲染,以实现最优用户体验。

Transitions
通过将状态更新包裹在 startTransition 中,我们可以告诉 React 可以推迟或中断渲染,以优先处理更重要的任务,以保持当前用户界面的交互体验。
import { useTransition } from "react";
function Button() {
const [isPending, startTransition] = useTransition();
return (
<button
onClick={() => {
urgentUpdate();
startTransition(nonUrgentUpdate);
}}
>
...
</button>
);
}我们可以将状态更新包裹在 startTransition 中。这告诉 React 该状态更新可能会导致对用户有干扰的视觉变化,因此 React 应该尽力保持当前 UI 的交互体验,在后台准备新状态而不立即提交更新。

React Server Components
Data Fetching
React 18 现在具有一个缓存函数,它可以记住包装函数调用的结果。如果在同一次渲染过程中使用相同的参数再次调用相同的函数,它将使用记忆化的值,无需再次执行该函数。
import { cache } from "react";
export const getUser = cache(async (id) => {
const user = await db.user.findUnique({ id });
return user;
});
getUser(1);
getUser(1); // Called within same render pass: returns memoized result.