首页 / React 19 入门教程 / useDeferredValue

React 19 入门教程

useDeferredValue

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

ReactuseDeferredValue延迟更新性能优化防抖并发渲染

40. useDeferredValue

本节目标:学会用 useDeferredValue 延迟非紧急的渲染更新,让输入框在繁重 UI 中保持流畅。

问题:输入框被慢组件拖慢

一个输入框 + 一个根据输入渲染的慢列表。用户每敲一个键,列表重新渲染,输入框就卡一下。

function SearchPage() {
  const [query, setQuery] = useState('');
  return (
    <>
      <input value={query} onChange={e => setQuery(e.target.value)} />
      <SlowList text={query} />  {/* 渲染 250 个慢组件 */}
    </>
  );
}

query 变化触发两个更新:输入框要显示新值(紧急),SlowList 要重新渲染(不紧急)。但 React 会把它们一起处理,导致输入框被 SlowList 拖慢。

useDeferredValue:给值加个”延迟版”

useDeferredValue 返回一个”延迟版本”的值。原始值变化时,延迟值不会立即跟上,而是等紧急更新完成后再更新。

import { useDeferredValue } from 'react';

function SearchPage() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);

  return (
    <>
      <input value={query} onChange={e => setQuery(e.target.value)} />
      <SlowList text={deferredQuery} />
    </>
  );
}

现在 query 立即更新(输入框流畅),deferredQuery 延后更新(SlowList 稍后渲染)。用户打字不卡了。

Note

useDeferredValue 不是防抖。它没有固定延迟,而是等浏览器空闲时才更新。设备快就几乎无感,设备慢就明显滞后。

给旧内容加个视觉提示

延迟期间,列表显示的是旧数据。可以加个视觉提示让用户知道数据不是最新的:

function SearchPage() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;

  return (
    <>
      <input value={query} onChange={e => setQuery(e.target.value)} />
      <div style={{ opacity: isStale ? 0.5 : 1 }}>
        <SlowList text={deferredQuery} />
      </div>
    </>
  );
}

列表变半透明,新数据加载完成后恢复。用户能感知到”正在更新”,但不会觉得卡。

useDeferredValue vs useTransition

这俩解决的问题很像,但用法不同:

useTransitionuseDeferredValue
触发方式包裹 state 更新函数包裹值
控制方你知道什么时候更新React 自动决定何时更新
适用场景点击、提交等事件触发输入、滚动等连续变化
返回值[isPending, startTransition]延迟后的值
// useTransition:你知道更新什么时候发生
function TabContainer() {
  const [isPending, startTransition] = useTransition();
  const [tab, setTab] = useState('about');

  function handleClick(newTab) {
    startTransition(() => setTab(newTab));  // 你控制
  }
}

// useDeferredValue:React 自动延迟
function SearchPage() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);  // React 控制
}

经验法则:如果你能控制 state 更新的时机,用 useTransition;如果值来自外部(props、输入),用 useDeferredValue。

配合 memo 使用才有用

useDeferredValue 单独用没用。它需要配合 memo 包裹的子组件才能跳过不必要的重渲染:

import { memo } from 'react';

const SlowList = memo(function SlowList({ text }) {
  // 渲染 250 个组件
  return (
    <ul>
      {items.map(item => <SlowItem key={item.id} text={text} />)}
    </ul>
  );
});

原理:deferredQuery 没变时,SlowList 的 props 没变,memo 跳过渲染。deferredQuery 变了才重新渲染。

没有 memo 的话,父组件每次渲染子组件也会重新渲染,useDeferredValue 就白用了。

Warning

传给 useDeferredValue 的值最好是原始类型或渲染外部创建的对象。每次渲染创建新对象会导致不必要的后台重渲染。

与 Suspense 的配合

useDeferredValue 和 Suspense 集成得很好。如果延迟更新导致子组件挂起(suspend),React 不会显示 fallback,而是继续显示旧内容:

function SearchPage() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);

  return (
    <>
      <input value={query} onChange={e => setQuery(e.target.value)} />
      <Suspense fallback={<p>加载中……</p>}>
        <SearchResults query={deferredQuery} />
      </Suspense>
    </>
  );
}

用户输入时,旧结果保持显示,新结果在后台加载。加载完成后直接替换,不会出现”加载中”的闪烁。

和防抖/节流的区别

你可能会想:这不就是防抖吗?不完全是:

  • 防抖:固定延迟 300ms 再更新。不管设备快慢都等 300ms。
  • 节流:固定频率更新,比如每秒最多 10 次。
  • useDeferredValue:没有固定延迟。React 会在紧急更新完成后立即尝试更新,设备快就几乎无延迟。

而且 useDeferredValue 的更新是可中断的。如果用户继续输入,React 会放弃上一次的延迟渲染,重新开始。防抖和节流做不到这一点——它们只是推迟,不取消。

Tip

如果你要优化的是网络请求次数(不是渲染性能),防抖/节流更合适。useDeferredValue 优化的是渲染性能,不会减少请求次数。