← 中文资源

简体中文版本

React — 组件挂起但没有降级 UI

修复 React 挂起无降级 UI 的错误与水合期挂起闪烁,正确放置 Suspense 边界,并为紧急更新使用 startTransition。

阅读英文原文

症状

  错误直接点名缺失件:“A component suspended while rendering, but no fallback UI was specified”——加上边界即可。更糟的是无报错的沉默变体:唯一边界包住整个应用,懒加载设置面板时整页消失在一个 spinner 后面。要画的地图是:哪些组件会挂起(lazy 导入、数据 hook)与边界相对它们的位置——边界远离挂起子组件等于全页降级,紧贴挂起单元等于局部 spinner、外壳完好。水合期挂起子组件没有边界时,整棵树被迫退回客户端渲染,表现为页面先服务端闪现再整树重挂。

诊断思路

  先读错误区分两种变体:渲染期缺边界补边界,同步输入期挂起改用 transition;再绘制边界与 lazy/suspending 组件的相对位置图;最后给更新按紧急度分类——触发挂起的输入驱动更新必须是 transition,紧急渲染按设计不能等降级 UI。

根因

  组件挂起时 React 向上找最近的 Suspense 边界并渲染其降级 UI。没有边界、或边界放得太高,要么抛出 missing-fallback 错误,要么整个子树藏进一个 spinner——片段加载让整页空白。

修复与验证

  边界紧贴每个挂起单元:Timeline 与 Recommendations 各自的 Skeleton,Header 与 Shell 在一切边界之外始终渲染;触发数据加载的输入用 useTransition 的 start 包裹 setTab,旧标签页保持可交互直到新内容提交;降级 UI 设计要能撑住几何——匹配加载后布局的骨架屏让“加载”本身不再制造布局偏移与滚动重置。验证时懒加载片段期间外壳与兄弟区域保持渲染;触发数据加载的输入保持响应、旧内容驻留到新内容提交。

代码示例

Error: A component suspended while rendering, but no fallback UI
was specified. Add a <Suspense boundary=...> around it.
function ProfilePage() {
  return (
    <Shell>                       {/* outside any boundary: always rendered */}
      <Header />
      <Suspense fallback={<TimelineSkeleton />}>
        <Timeline />               {/* suspends on data */}
      </Suspense>
      <Suspense fallback={<RecoSkeleton />}>
        <Recommendations />        {/* suspends independently */}
      </Suspense>
    </Shell>
  );
}
const [isPending, start] = useTransition();

function onSelect(tab) {
  start(() => {
    setTab(tab);   // suspending data fetch marked non-urgent
  });
}
// old tab stays interactive and visible until the new one commits