React 学习站
React›前沿与实践›前沿

useTransition 与 useDeferredValue

前沿前沿与实践

一句话定义

两个面向用户的并发 API:useTransition 把某次更新标记为非紧急(并告诉你是否进行中),useDeferredValue 把某个值的下游渲染标记为非紧急(值本身立即更新)——一个控制「动作」,一个控制「值」。

为什么重要

它们是并发渲染从理论到手感的最短路径:两三行代码就能消除「输入卡顿」和「fallback 闪烁」两类高频体验问题,也是 KP-022 概念的最佳实验场。

前置知识

核心概念

  • isPending:useTransition 返回的布尔——过渡渲染尚未提交时为 true,用于显示「旧的还在用,新的在路上」。
  • startTransition(fn):fn 内的 setState 都以过渡优先级排队。
  • useDeferredValue(value):返回「滞后一拍」的版本:value 变化时先用旧值渲染(快),再用新值重渲染(过渡优先级)。
  • 与 debounce 的本质区别:debounce 固定延迟、丢中间帧;deferred 不丢帧、不固定延迟——只是让慢的渲染让路。
  • 适用判据:更新结果可以晚一点出现(列表过滤、图表、搜索结果);输入框回显这类「必须立即」的绝不放 transition 里。

原理 / 机制

useTransition 完整形状:

jsxfunction FilterableList({ items }) {
  const [query, setQuery] = useState('');
  const [isPending, startTransition] = useTransition();

  function onChange(e) {
    setQuery(e.target.value);            // 输入框:立即
    startTransition(() => {
      setAppliedQuery(e.target.value);   // 列表:过渡(可让路、可重放)
    });
  }

  return (
    <>
      <input value={query} onChange={onChange} />
      <div className={isPending ? 'list--updating' : ''}>
        <List items={filter(items, appliedQuery)} />
      </div>
    </>
  );
}

useDeferredValue 的等价改写(更少样板,适合「值驱动」的场景):

jsxfunction FilterableList({ items, query }) {
  const deferredQuery = useDeferredValue(query);  // query 立即用,deferredQuery 滞后

  return <List items={filter(items, deferredQuery)} />;
  // 父组件用 query 传给输入框,用 deferredQuery 传给重计算部分
}

两个 API 的选择:

useTransitionuseDeferredValue
控制对象一组 setState 调用一个值的下游渲染
能显示进行中✅ isPending❌(可自行比对两值差异)
代码量多(拆两个 state)少(一行)
典型场景分页/Tab 切换 + 数据加载搜索输入、滑块联动重计算

与 Suspense 的配合(消除 fallback 闪烁,kp-020 误区的解法):

jsxfunction Tab({ tab }) {
  const [isPending, startTransition] = useTransition();
  function select(next) {
    startTransition(() => setCurrentTab(next));
  }
  return (
    <>
      {tabs.map(t => (
        <button key={t.id} onClick={() => select(t.id)}
                aria-current={t.id === tab}>
          {t.label} {t.id === tab && isPending ? '…' : ''}
        </button>
      ))}
      <Suspense fallback={null}>      {/* 过渡期间沿用旧内容,不闪骨架 */}
        <TabContent tab={tab} />
      </Suspense>
    </>
  );
}

过渡更新期间,React 保留已显示的旧 UI 继续可交互,新 UI 就绪后整体切换——这就是「不闪烁」的机制。

直观类比

useTransition 像餐厅「已下单,厨艺中」的小票:客人(用户)的催单(输入)立即被记录,厨房(React)先把催单回显出来,大菜(重渲染列表)慢慢烧,小票上的「进行中」(isPending)告诉客人别急。useDeferredValue 像电视的插播字幕:新闻正文(value)实时更新,下方的滚动条(deferred)晚一拍跟上——没人会觉得字幕卡了新闻。debounce 则是「每 3 秒才允许点一次菜」,快是快,但顾客的手被限制住了。

实例 / 案例

组合拳:搜索页的完整体验优化(kp-019 案例的进阶版):

jsxfunction SearchPage({ items }) {
  const [query, setQuery] = useState('');
  const deferred = useDeferredValue(query);
  const isStale = query !== deferred;           // 手工"pending"感

  const filtered = useMemo(
    () => items.filter(i => i.name.toLowerCase().includes(deferred.toLowerCase())),
    [items, deferred]
  );

  return (
    <>
      <input value={query} onChange={e => setQuery(e.target.value)} autoFocus />
      <ul className={isStale ? 'opacity-60' : ''}>
        {filtered.map(i => <Row key={i.id} item={i} />)}
      </ul>
    </>
  );
}

输入零卡顿(query 立即回显)、结果自动错峰、过期结果半透明提示——三件事各一行代码。

常见误区

  • 把输入框自己的 setState 包进 transition:输入回显是最高优先级,包了反而让打字「追不上手」。规则:只有「结果展示」的更新进过渡。
  • 把 transition 当性能优化器:它不省工作量(kp-022 误区);大数据列表该虚拟化还是虚拟化。
  • useDeferredValue 传了新对象:deps 按引用比较,内联对象每次都是「新值」,延迟机制失效;传原始值或 useMemo 的引用。
  • 以为 isPending 能区分「哪个更新在进行」:它是整个过渡队列的开关灯;复杂场景拆多个 state 分组进不同 transition。
  • 滥用 isPending 全屏 loading:过渡的本意是「旧 UI 继续可用」;全屏遮罩等于自废武功。

自测题

  1. 用一句话区分两个 API 的控制对象;什么时候必须用 useTransition(而不能用 useDeferredValue)?
  2. 为什么输入框 setState 不能放进 startTransition?写出正确拆分。
  3. useDeferredValue 与 debounce 在「快速连续输入」时的行为差异?
  4. 过渡期间 Suspense 为什么不该显示 fallback?React 显示的是什么?
  5. 把 kp-023 案例里 query !== deferred 的原理讲清楚(两次渲染各用什么值)。

与其他知识点的关系

  • 向前:kp-022 的用户级出口。
  • 向后:kp-025 的 Actions 自动采用过渡优先级提交表单更新;kp-019 把它们纳入性能手段对照表;kp-020 的闪烁问题在此获得标准解。

延伸阅读

  • useTransition:<https://react.dev/reference/react/useTransition>
  • useDeferredValue:<https://react.dev/reference/react/useDeferredValue>
  • Keep the UI responsive with transitions(官方教程):<https://react.dev/learn/react-api-reference#transitions>