首页 / React 19 入门教程 / useTransition 并发

React 19 入门教程

useTransition 并发

本教程共 50 篇 · 第 39 篇 · 更新于 2026-07-29 · 约 9 分钟阅读

ReactuseTransitionstartTransition并发渲染Actions性能优化

39. useTransition 并发

本节目标:学会用 useTransition 把非紧急的状态更新降级,让 UI 在繁重操作中保持响应。

问题:紧急更新被卡住

React 默认把所有状态更新当作紧急的。用户点击切换标签页,如果新标签内容要渲染 500 个组件,这 500 个会一口气渲染完,期间输入框会被卡住。

function TabContainer() {
  const [tab, setTab] = useState('about');

  return (
    <>
      <button onClick={() => setTab('about')}>关于</button>
      <button onClick={() => setTab('posts')}>文章(慢)</button>
      <button onClick={() => setTab('contact')}>联系</button>
      {tab === 'about' && <AboutTab />}
      {tab === 'posts' && <PostsTab />}  {/* 假设有 500 条数据 */}
      {tab === 'contact' && <ContactTab />}
    </>
  );
}

点”文章”按钮,PostsTab 开始渲染。如果 PostsTab 很慢(比如每条数据都要复杂计算),整个页面就冻住了。用户想改主意点”联系”都点不了。

useTransition:把更新标记成”可中断”

useTransition 返回两样东西:isPending(是否正在等待)和 startTransition(标记非紧急更新)。

import { useTransition } from 'react';

function TabContainer() {
  const [tab, setTab] = useState('about');
  const [isPending, startTransition] = useTransition();

  function selectTab(nextTab) {
    startTransition(() => {
      setTab(nextTab);
    });
  }

  return (
    <>
      <button onClick={() => selectTab('about')}>关于</button>
      <button onClick={() => selectTab('posts')}>文章(慢)</button>
      <button onClick={() => selectTab('contact')}>联系</button>
      {tab === 'about' && <AboutTab />}
      {tab === 'posts' && <PostsTab />}
      {tab === 'contact' && <ContactTab />}
    </>
  );
}

setTab 放进 startTransition,React 就知道这个更新不紧急。如果用户正在 PostsTab 渲染过程中又点了”联系”,React 会中断 PostsTab 的渲染,先去处理”联系”这个紧急操作。

Note

startTransition 里的函数必须同步执行。如果是异步的(比如 await 之后的 setState),需要再包一层 startTransition

显示 pending 状态

isPending 告诉你是否有 Transition 正在进行。可以用来给用户反馈:

function TabButton({ action, children, isActive }) {
  const [isPending, startTransition] = useTransition();

  if (isActive) return <b>{children}</b>;
  if (isPending) return <b style={{ color: '#999' }}>{children}</b>;

  return (
    <button onClick={() => {
      startTransition(async () => {
        await action();
      });
    }}>
      {children}
    </button>
  );
}

点击”文章”按钮后,按钮变灰色(pending 状态),PostsTab 渲染完成后恢复正常。

Actions:React 19 的写法

React 19 把 startTransition 里的函数叫做 Action。Action 可以包含异步操作:

function CheckoutForm() {
  const [isPending, startTransition] = useTransition();
  const [quantity, setQuantity] = useState(1);

  function updateQuantity(newQty) {
    startTransition(async () => {
      const saved = await updateQuantityOnServer(newQty);
      startTransition(() => {
        setQuantity(saved);  // 需要再包一层
      });
    });
  }

  return (
    <div>
      <input
        type="number"
        defaultValue={1}
        onChange={e => updateQuantity(Number(e.target.value))}
      />
      {isPending && <span>更新中……</span>}
      <p>数量:{quantity}</p>
    </div>
  );
}

注意 await 之后的 setQuantity 还要再包一层 startTransition。这是当前的限制,未来可能会改进。

Warning

startTransition 里的函数是立即执行的,不是延迟执行。它只是告诉 React”这里面发生的 state 更新是不紧急的”。

哪些场景适合 useTransition

  • 标签页切换:新内容渲染慢,允许用户中途改主意
  • 搜索筛选:筛选结果渲染慢,输入框不能卡
  • 路由导航:新页面数据量大,导航可以被打断
  • 大型列表渲染:渲染几千条数据时不阻塞交互

不适合的场景:

  • 受控输入框:输入框的值必须同步更新,不能降级
  • 简单的 state 更新:本来就很快,不需要降级
  • 动画相关:需要精确控制时序,不适合并发
// ❌ 错误:不能用于受控输入
function SearchInput() {
  const [text, setText] = useState('');
  const [isPending, startTransition] = useTransition();

  return (
    <input
      value={text}
      onChange={e => {
        startTransition(() => {
          setText(e.target.value);  // 输入会延迟
        });
      }}
    />
  );
}

useTransition 与 Suspense 的配合

useTransition 配合 Suspense 可以实现一个很酷的效果:不显示 loading fallback,而是保持旧内容直到新内容就绪

function App() {
  const [tab, setTab] = useState('about');
  const [isPending, startTransition] = useTransition();

  return (
    <Suspense fallback={<p>加载中……</p>}>
      <button onClick={() => startTransition(() => setTab('about'))}>
        关于
      </button>
      <button onClick={() => startTransition(() => setTab('posts'))}>
        文章
      </button>
      {tab === 'about' && <AboutTab />}
      {tab === 'posts' && <PostsTab />}
    </Suspense>
  );
}

点”文章”后,页面不会变成”加载中……”。用户看到的是旧的内容(关于页面),PostsTab 在后台加载。等新内容准备好了,React 才切换过去。

如果 PostsTab 加载很快(< 500ms),用户完全感觉不到延迟。如果加载慢,会先显示旧内容,避免白屏或闪烁。

深入理解并发渲染

React 19 的并发渲染允许 React 同时准备多个版本的 UI:

  1. 用户点”文章” → React 开始在后台渲染 PostsTab
  2. 用户又点”关于” → React 中断 PostsTab,渲染 AboutTab
  3. PostsTab 的渲染被丢弃,不会显示给用户

这个能力来自 React 的”双缓冲”机制。React 可以在内存里构建新的渲染树,不打扰当前显示的内容。构建完成后,一次性提交到 DOM。

Tip

并发渲染不是多线程。JavaScript 还是单线程的。React 是利用调度和优先级,在浏览器空闲时做渲染工作,遇到紧急事件时让出控制权。