人人都会AI编程

useTransition /useDeferredValue:并发渲染优先级控制

更新时间:2026-07-11

在 React 18 中,并发渲染(Concurrent Rendering)让 UI 更新有了优先级的概念。useTransitionuseDeferredValue 就是两个帮助我们标记低优先级更新的 Hook,它们的核心作用是保持界面交互的流畅性,防止耗时计算阻塞用户操作。

为什么需要优先级控制

假设你有一个搜索框,用户输入时需要在下方展示筛选后的列表。如果列表很长,筛选计算又重,每次按键都会触发一次耗时的渲染,导致输入框出现明显卡顿——这是典型的“输入阻塞渲染”问题。

在并发模式下,React 会将输入更新(用户正在打字)视为高优先级,将列表更新视为低优先级。当两者同时发生时,React 可以中断低优先级的渲染,优先处理高优先级的更新,处理完后再继续低优先级的渲染。这样用户打字始终流畅。

useTransitionuseDeferredValue 就是让开发者显式告诉 React:“这个更新不重要,可以滞后处理”的工具。

useTransition:标记某个状态更新为“过渡任务”

useTransition 返回一个包含两个值的数组:

  • isPending:布尔值,表示低优先级更新是否正在等待完成(可用于显示加载状态)。
  • startTransition:函数,将包裹在其中的状态更新标记为低优先级。

基本用法:

import { useState, useTransition } from 'react';

function SearchPage() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleChange = (e) => {
    const value = e.target.value;
    setQuery(value); // 高优先级:立即更新输入框的文字

    startTransition(() => {
      // 低优先级:延迟更新筛选后的列表
      const filtered = performHeavyFilter(value);
      setResults(filtered);
    });
  };

  return (
    <div>
      <input value={query} onChange={handleChange} placeholder="搜索..." />
      {isPending && <span>计算中...</span>}
      <ResultsList data={results} />
    </div>
  );
}

这里的关键点:

  • setQuery(value)startTransition 外部执行,React 认为它是紧急更新(用户打字),会立即同步处理,保证输入框跟手。
  • setResults(filtered) 被包裹在 startTransition 内,成为了低优先级更新。如果用户在列表渲染完成前又输入了新字符,React 会放弃当前渲染,直接用新的 value 重新计算并渲染。
  • isPending 可以用来向用户展示一个友好的“正在处理”状态,避免因列表短暂未更新而产生疑惑。

不使用 useTransition 会怎样?

同样场景,如果直接将 setResults 放在事件处理中(与 setQuery 相同优先级),React 会把两个状态更新合并处理,导致输入框的更新会被复杂计算卡住。使用 useTransition 后,输入框永远流畅,列表最多就是延迟一点出现。

适用场景:

  • 搜索框输入时更新搜索结果。
  • 切换 Tab 页时立即切换按钮高亮,但内容区可以稍后渲染。
  • 实时过滤长列表或图表。

useDeferredValue:延迟使用某个值

useDeferredValue 接收一个值,返回一个“延迟版本”。当原始值发生变化时,延迟值会保持旧值一段时间,直到浏览器空闲或更高优先级的工作完成,再更新为新值。

基本用法:

import { useState, useDeferredValue } from 'react';

function ProductList({ products, filterText }) {
  const deferredFilter = useDeferredValue(filterText);

  // 使用延迟后的值进行计算,避免每次输入都重新过滤
  const filteredProducts = useMemo(
    () => products.filter(p => p.name.includes(deferredFilter)),
    [products, deferredFilter]
  );

  return (
    <ul>
      {filteredProducts.map(p => (
        <li key={p.id}>{p.name}</li>
      ))}
    </ul>
  );
}

这里与 useTransition 的区别在于,useTransition 是由你主动包裹更新来降低优先级,而 useDeferredValue 是让某个值的更新自动变为低优先级,更方便与现有代码集成。

什么时候用 useTransition,什么时候用 useDeferredValue

  • 你能控制状态更新的地方(比如事件处理函数内),使用 useTransition,因为它还能提供 isPending 反馈状态。
  • 如果某个值来自第三方组件、或是现有库的状态,你不方便改写状态更新代码,则用 useDeferredValue 来“延迟消费”它。

两者的协作示例

一个典型搜索场景,结合两者:

function SearchPage({ list }) {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  const [isPending, startTransition] = useTransition();

  const handleChange = (e) => {
    setQuery(e.target.value); // 立即更新输入框
    startTransition(() => {
      // 低优先级更新(但这里其实可以被 useDeferredValue 替代)
      // 仅用于展示 isPending
    });
  };

  const filteredList = useMemo(
    () => list.filter(item => item.text.includes(deferredQuery)),
    [list, deferredQuery]
  );

  return (
    <div>
      <input value={query} onChange={handleChange} />
      {isPending && <small>正在更新...</small>}
      <ItemList items={filteredList} />
    </div>
  );
}

需要注意的几点

  • 这些 Hook 仅在并发模式下有效(React 18 默认启用并发特性),必须使用 createRoot 渲染。
  • 不要将 startTransition 用于需要立即反馈的操作(如表单提交),那应该保持同步更新。
  • useDeferredValue 返回的值可能在一段时间内是“过时”的,因此要确保你的 UI 可以安全地使用旧数据(比如仅用于展示,不会导致误操作)。
  • 渲染过程中的重计算仍然会消耗 CPU,useTransition 不能优化计算本身,只是调整了计算的时机和顺序,避免阻塞高优先级任务。

总结

useTransitionuseDeferredValue 是 React 并发模式下用于精细控制 UI 响应优先级的工具。它们让输入框这类紧急交互不再被复杂的列表渲染所拖累,大幅提升了用户体验。在实际开发中,凡是遇到“输入反馈慢”“切换不流畅”的场景,都可以优先考虑使用这两个 Hook 来优化。