掌握挂起机制、组件的懒加载以及打包产物的分割。
在 Kodokon 中打开本课Suspense 依赖一个鲜为人知的机制:一个尚未拥有其数据或代码的组件会挂起它的渲染。在底层,当前的实现会抛出一个 Promise,React 会像拦截错误那样拦截它。React 随后会向上寻找最近的 <Suspense> 边界,显示它的 fallback,然后在 Promise 解决后重放该子树的渲染。这是一个实现细节(永远不要自己抛出 Promise),但它解释了所有可观察到的行为:这个边界就像针对异步工作的一个声明式 catch。历史上的典型用例是使用 React.lazy 进行代码分割。
import { lazy, Suspense } from 'react';
const Chart = lazy(() => import('./Chart'));
function Dashboard() {
return (
<Suspense fallback={<Spinner />}>
<Chart points={data} />
</Suspense>
);
}lazy 期望一个返回 Promise 的函数,该 Promise 解决为一个带有 default 导出的模块。分割本身是打包器的工作:每个动态 import() 都会成为一个分割点并生成一个独立的 chunk,按需加载。在真实代码库中一个常见的细节:你的组件往往是具名导出。这时你需要即时地对模块进行改造。
const Chart = lazy(() =>
import('./charts').then((module) => ({
default: module.Chart,
}))
);生产环境中棘手的部分:在一次导航期间,已经显示的内容可能会再次挂起(新的路由、新的 chunk)并被 fallback 粗暴地替换。自 React 18 起,用 startTransition 包裹的更新改变了这一行为:React 会在新渲染于后台挂起时把之前的界面保留在屏幕上,只对从未展示过的内容显示 fallback。边界还能在深度上组合:多个嵌套的 <Suspense> 边界让你能一块区域接一块区域地揭示界面,从全局骨架一直细化到细节。
const [isPending, startTransition] = useTransition();
function openTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// The current tab stays visible while the new
// one loads, instead of dropping back to the fallback.React.lazy 的函数必须返回什么?