跳转到主要内容

并发与 Suspense

并发渲染让 React 可以把“计算下一次 UI”分片处理,并在必要时中断/重试,从而把 CPU 时间更优先地分配给用户正在做的事(输入、点击、滚动)。 Suspense 则让“某块 UI 尚未就绪”成为可组合的结构能力:你可以用边界把未就绪部分隔离,先展示可用部分。

1) 并发到底改变了什么?

并发改变的是 React 的“调度策略”,不是你的组件 API。最重要的心智变化只有一句话:render 只是计算,可能会被打断并重做。 这也是为什么 React 一直强调 render 必须是纯函数式的 UI 计算。

关键不变量
  • 不要在 render 阶段触发副作用(网络请求、写 DOM、订阅、打点)。
  • 副作用属于 commit 后:用 Effect 表达资源生命周期。
  • 把“性能问题”归因到某个 Hook 往往不靠谱,先看更新优先级与边界设计。

2) 优先级:为什么 Transition 不是“防抖”

并发最实用的能力是优先级。用户输入是紧急的;“根据输入算出大结果”通常不是紧急的。 过渡更新(transition)让 React 知道:可以在不影响输入响应的前提下慢慢把结果算出来。

TypeScript
import { useMemo, useState, useTransition } from 'react';

export function SearchList({ items }: { items: string[] }) {
  const [query, setQuery] = useState('');
  const [isPending, startTransition] = useTransition();

  const filtered = useMemo(() => {
    const q = query.trim().toLowerCase();
    if (!q) return items;
    return items.filter((x) => x.toLowerCase().includes(q));
  }, [items, query]);

  return (
    <div>
      <input
        value={query}
        onChange={(e) => {
          const next = e.target.value;
          startTransition(() => setQuery(next));
        }}
        placeholder="输入保持响应,结果可以稍后更新"
      />
      {isPending && <p>更新中…</p>}
      <ul>{filtered.map((x) => <li key={x}>{x}</li>)}</ul>
    </div>
  );
}

更细的契约与边界请对照:useTransition useDeferredValue

3) Suspense:把“就绪度”变成 UI 结构

Suspense 的核心不是 fallback,而是边界:你用边界定义“哪些内容可以先展示、哪些内容可以后到”。 这让渐进式渲染变成一种可组合的结构能力,而不是每个页面手写一套 loading 状态机。

如果你想系统掌握 Suspense 的边界放置、与缓存/SSR 的关系,建议看: Suspense(深挖)

4) 组合:并发 + Suspense 的“正确目标”

  • 把输入/点击保持为紧急更新,让交互永远即时。
  • 把大计算、路由切换、数据/代码就绪等当作过渡更新。
  • 用 Suspense 把“未就绪”隔离在合理边界内,避免整个页面闪回骨架。
快速自检(工程落地)
  • 你的边界是不是从“体验目标”倒推,而不是从组件树随手包一层?
  • 你有没有稳定缓存/去重机制来支撑“重试渲染”?
  • 你是否为错误提供了可恢复路径(重试/回退/刷新)?

下一步

并发与 Suspense - Explanation - React 文档 - React 文档