useDeferredValue
本教程共 50 篇 · 第 40 篇 · 更新于 2026-07-29 · 约 7 分钟阅读
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
这俩解决的问题很像,但用法不同:
| useTransition | useDeferredValue | |
|---|---|---|
| 触发方式 | 包裹 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优化的是渲染性能,不会减少请求次数。