useDeferredValue 和 useTransition 都是 React 18 为并发渲染 设计的核心 Hook,目的都是「避免耗时更新阻塞页面、提升用户交互体验」,但二者的核心定位、使用方式、作用对象完全不同------简单来说:
useDeferredValue是「延迟更新某个值」(针对数据/状态);useTransition是「标记某个更新为低优先级」(针对更新操作)。
下面用「核心区别+场景示例+对比表」的方式讲透,新手也能快速区分。
一、核心区别(先抓本质)
| 特性 | useDeferredValue | useTransition |
|---|---|---|
| 核心作用 | 对已有值创建一个"延迟更新的副本",高优先级更新(如输入)优先执行,低优先级的副本稍后更新 | 把状态更新标记为"非紧急更新"(transition),不阻塞高优先级交互(如输入、点击) |
| 作用对象 | 针对「值/状态本身」(如列表数据、筛选结果) | 针对「状态更新操作」(如 setState 调用) |
| 使用方式 | 基于已有值生成延迟值,直接使用这个延迟值渲染 | 包裹 setState 调用,标记为低优先级更新 |
| 返回值 | 返回延迟更新的"副本值" | 返回 [isPending, startTransition](加载状态 + 触发低优先级更新的函数) |
| 触发时机 | 高优先级更新完成后,自动更新延迟值 | 手动调用 startTransition 触发低优先级更新 |
| 核心场景 | 「数据派生」场景(如输入框筛选长列表) | 「状态更新」场景(如点击按钮切换大量数据、表单提交) |
二、逐个拆解:用法+场景(实战理解)
1. useDeferredValue:延迟更新"派生值"(被动)
核心逻辑:当某个值(如输入框内容)频繁更新时,基于它生成一个"延迟更新的副本",组件优先渲染最新的原始值(保证交互不卡),等主线程空闲后再渲染延迟值(更新最终结果)。
典型场景:输入框实时筛选长列表(输入是高优先级,列表筛选是低优先级)。
jsx
import { useState, useDeferredValue } from 'react';
function SearchList() {
// 1. 高优先级:输入框内容(实时更新,不阻塞)
const [inputValue, setInputValue] = useState('');
// 2. 低优先级:延迟更新的筛选关键词(等输入完成后再更新)
const deferredValue = useDeferredValue(inputValue, {
timeoutMs: 200 // 可选:延迟最长等待时间(超过则强制更新)
});
// 3. 基于延迟值筛选列表(耗时操作,不会阻塞输入)
const filteredList = useMemo(() => {
// 模拟长列表筛选(耗时计算)
return longList.filter(item => item.includes(deferredValue));
}, [deferredValue]); // 依赖延迟值,而非原始输入值
return (
<div>
{/* 输入框:实时响应,不卡顿 */}
<input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="搜索..."
/>
{/* 列表:基于延迟值渲染,避免输入时卡顿 */}
<ul>
{filteredList.map(item => <li key={item}>{item}</li>)}
</ul>
</div>
);
}
// 模拟10万条长列表(用于测试耗时)
const longList = Array.from({ length: 100000 }, (_, i) => `item-${i}`);
关键效果 :输入时,输入框实时显示内容(高优先级),列表筛选会"延迟一步"更新,但输入操作完全不卡;若没有 useDeferredValue,输入时列表实时筛选会阻塞主线程,导致输入卡顿。
2. useTransition:标记"低优先级更新"(主动)
核心逻辑 :把耗时的 setState 包裹在 startTransition 中,标记为"非紧急更新",React 会优先处理高优先级交互(如输入、点击),等主线程空闲后再执行这个更新,同时通过 isPending 显示加载状态。
典型场景:点击按钮切换大量数据(如切换分页、展开长列表)。
jsx
import { useState, useTransition } from 'react';
function BigList() {
const [list, setList] = useState([]);
// 1. 获取加载状态 + 低优先级更新函数
const [isPending, startTransition] = useTransition({
timeoutMs: 300 // 可选:超过300ms仍未执行则强制更新
});
// 2. 点击按钮触发低优先级更新(不阻塞页面)
const loadBigList = () => {
// 包裹耗时的 setState,标记为 transition
startTransition(() => {
// 模拟生成10万条数据(耗时操作)
const newList = Array.from({ length: 100000 }, (_, i) => `item-${i}`);
setList(newList); // 低优先级更新,不阻塞交互
});
};
return (
<div>
{/* 按钮:点击后立即响应,不卡顿 */}
<button onClick={loadBigList} disabled={isPending}>
{isPending ? '加载中...' : '加载10万条数据'}
</button>
{/* 列表:加载完成后渲染,期间页面不卡 */}
<ul>
{list.map(item => <li key={item}>{item}</li>)}
</ul>
</div>
);
}
关键效果:点击按钮后,按钮立即显示"加载中"(高优先级),生成10万条数据的更新被标记为低优先级,期间可以正常操作页面(如滚动、输入),不会因耗时计算阻塞。
三、什么时候用哪个?(快速决策)
用 useDeferredValue 的场景
✅ 有一个「源值」(如输入框内容),基于它派生另一个「耗时计算的值」(如筛选列表);
✅ 希望"源值实时更新,派生值延迟更新",避免派生计算阻塞源值的交互;
✅ 核心是"被动延迟值的更新",无需手动触发。
用 useTransition 的场景
✅ 主动触发一个「耗时的状态更新」(如 setState 生成大量数据);
✅ 希望这个更新不阻塞高优先级交互,且需要显示加载状态;
✅ 核心是"主动标记更新为低优先级",手动控制更新时机。
二者可配合使用的场景
比如"输入框筛选长列表"既可以用 useDeferredValue,也可以结合 useTransition 优化:
jsx
// 混合使用:输入延迟 + 更新标记为低优先级
const [inputValue, setInputValue] = useState('');
const deferredValue = useDeferredValue(inputValue);
const [isPending, startTransition] = useTransition();
const handleInput = (e) => {
// 输入更新标记为高优先级
setInputValue(e.target.value);
// 筛选更新标记为低优先级
startTransition(() => {
// 基于 deferredValue 筛选列表
});
};
四、关键注意事项
- 都不改变计算耗时:二者只是"调整更新优先级",不会减少筛选/生成数据的耗时,只是让耗时操作不阻塞交互;
- timeoutMs 兜底 :都支持
timeoutMs参数,超过该时间后,无论主线程是否空闲,都会强制更新(避免延迟太久影响体验); - 仅 React 18+ 支持:二者都是 React 18 并发渲染的特性,低版本无法使用;
- 不要滥用:仅用于"耗时更新"场景,普通更新(如按钮计数)无需使用,反而增加开销。
总结
- 核心区别 :
useDeferredValue:延迟「值的更新」,被动适配源值变化;useTransition:标记「更新操作」为低优先级,主动控制更新时机;
- 记忆口诀 :
- 「派生值延迟」用
useDeferredValue(如输入筛选); - 「更新操作标记」用
useTransition(如按钮加载数据);
- 「派生值延迟」用
- 核心目标:都是为了在并发渲染中,保证高优先级交互(输入、点击)不被低优先级耗时更新阻塞,提升用户体验。
如果有具体场景(比如"输入筛选长列表卡顿""切换标签页加载大量数据阻塞"),可以告诉我,我帮你写针对性的优化代码。