hook:useDeferredValue
- React 18 新增的并发特性 Hook,用于延迟更新某个值
- 核心作用: 让紧急更新(如输入框)优先渲染,延迟非紧急更新(如列表)
- 原理: 返回一个延迟版本的值,在紧急更新完成后才更新
基本语法
const deferredValue = useDeferredValue(value);
// value: 原始值
// deferredValue: 延迟更新的值(在紧急更新完成后才更新)
场景代码示例
-
【场景 1】搜索框优化(最常用):
-
输入时立即更新输入框,延迟更新搜索结果
❌ 不使用 useDeferredValue - 输入卡顿
import { useState } from "react"; function SearchApp() { const [query, setQuery] = useState(""); return ( <div> <input value={query} onChange={(e) => setQuery(e.target.value)} /> {/* query 变化时,立即渲染大列表,造成卡顿 */} <SearchResults query={query} /> </div> ); } function SearchResults({ query }) { const results = []; // 模拟大量计算 for (let i = 0; i < 10000; i++) { if (query && `Item ${i}`.includes(query)) { results.push(`Item ${i}`); } } return ( <ul> {results.map((item) => ( <li key={item}>{item}</li> ))} </ul> ); }✅ 使用 useDeferredValue - 输入流畅
import { useState, useDeferredValue } from "react"; function SearchApp() { const [query, setQuery] = useState(""); const deferredQuery = useDeferredValue(query); // 延迟 query 的值 return ( <div> <input value={query} onChange={(e) => setQuery(e.target.value)} /> {/* 使用延迟的值,不阻塞输入 */} <SearchResults query={deferredQuery} /> </div> ); } function SearchResults({ query }) { const results = []; for (let i = 0; i < 10000; i++) { if (query && `Item ${i}`.includes(query)) { results.push(`Item ${i}`); } } return ( <ul> {results.map((item) => ( <li key={item}>{item}</li> ))} </ul> ); } -
效果: 输入框立即响应,搜索结果延迟更新
-
-
【场景 2】实时过滤列表(常用):
-
过滤大列表时保持输入流畅
import { useState, useDeferredValue, useMemo } from "react"; function ProductFilter() { const [filter, setFilter] = useState(""); const deferredFilter = useDeferredValue(filter); // 使用延迟的值进行过滤 const filteredProducts = useMemo(() => { return products.filter((p) => p.name.toLowerCase().includes(deferredFilter.toLowerCase()), ); }, [deferredFilter]); // 判断是否正在延迟更新 const isStale = filter !== deferredFilter; return ( <div> <input type="text" value={filter} onChange={(e) => setFilter(e.target.value)} placeholder="搜索商品..." /> {isStale && <span> 搜索中...</span>} <ul style={{ opacity: isStale ? 0.5 : 1 }}> {filteredProducts.map((product) => ( <li key={product.id}>{product.name}</li> ))} </ul> </div> ); } const products = Array.from({ length: 10000 }, (_, i) => ({ id: i, name: `商品 ${i}`, }));
-
-
【场景 3】图表实时更新(常用):
-
滑动滑块时,立即更新滑块位置,延迟更新图表
import { useState, useDeferredValue } from "react"; function ChartWithSlider() { const [value, setValue] = useState(50); const deferredValue = useDeferredValue(value); return ( <div> <label> 当前值: {value} <input type="range" min="0" max="100" value={value} onChange={(e) => setValue(Number(e.target.value))} /> </label> {/* 图表使用延迟的值,不阻塞滑块移动 */} <ExpensiveChart value={deferredValue} /> </div> ); } function ExpensiveChart({ value }) { // 模拟复杂的图表渲染 const data = []; for (let i = 0; i < 1000; i++) { data.push({ x: i, y: Math.sin((i * value) / 100) * 100 }); } return ( <div> {/* 复杂的图表组件 */} <svg width="500" height="200"> {data.map((point, i) => ( <circle key={i} cx={point.x / 2} cy={point.y + 100} r="1" /> ))} </svg> </div> ); }
-
-
【场景 4】表格排序/分页(常用):
-
切换排序或分页时保持响应
import { useState, useDeferredValue, useMemo } from "react"; function DataTable({ data }) { const [sortKey, setSortKey] = useState("name"); const [sortOrder, setSortOrder] = useState("asc"); const deferredSortKey = useDeferredValue(sortKey); const deferredSortOrder = useDeferredValue(sortOrder); const sortedData = useMemo(() => { const sorted = [...data].sort((a, b) => { if (deferredSortOrder === "asc") { return a[deferredSortKey] > b[deferredSortKey] ? 1 : -1; } return a[deferredSortKey] < b[deferredSortKey] ? 1 : -1; }); return sorted; }, [data, deferredSortKey, deferredSortOrder]); const isStale = sortKey !== deferredSortKey || sortOrder !== deferredSortOrder; return ( <div> <select value={sortKey} onChange={(e) => setSortKey(e.target.value)}> <option value="name">按名称</option> <option value="price">按价格</option> <option value="date">按日期</option> </select> <button onClick={() => setSortOrder(sortOrder === "asc" ? "desc" : "asc")} > {sortOrder === "asc" ? "升序" : "降序"} </button> {isStale && <div>排序中...</div>} <table style={{ opacity: isStale ? 0.5 : 1 }}> <tbody> {sortedData.map((item) => ( <tr key={item.id}> <td>{item.name}</td> <td>{item.price}</td> </tr> ))} </tbody> </table> </div> ); }
-
-
【场景 5】地图缩放级别(进阶):
-
拖动地图时,立即响应,延迟更新标记点
import { useState, useDeferredValue } from "react"; function MapView() { const [zoom, setZoom] = useState(10); const [center, setCenter] = useState({ lat: 0, lng: 0 }); const deferredZoom = useDeferredValue(zoom); const deferredCenter = useDeferredValue(center); return ( <div> <div> <button onClick={() => setZoom(zoom + 1)}>放大</button> <button onClick={() => setZoom(zoom - 1)}>缩小</button> <span>缩放级别: {zoom}</span> </div> {/* 地图立即响应用户操作 */} <Map zoom={zoom} center={center} onMove={setCenter} /> {/* 标记点使用延迟的值,不阻塞地图拖动 */} <Markers zoom={deferredZoom} center={deferredCenter} /> </div> ); }
-
-
【场景 6】配合 memo 优化子组件(常用):
-
避免子组件频繁重新渲染
import { useState, useDeferredValue, memo } from "react"; function App() { const [text, setText] = useState(""); const deferredText = useDeferredValue(text); return ( <div> <input value={text} onChange={(e) => setText(e.target.value)} /> {/* 使用 memo 包裹,只有 deferredText 变化时才重新渲染 */} <SlowList text={deferredText} /> </div> ); } // 使用 memo 优化 const SlowList = memo(function SlowList({ text }) { console.log("SlowList 渲染"); const items = []; for (let i = 0; i < 250; i++) { items.push(<SlowItem key={i} text={text} />); } return <ul>{items}</ul>; }); function SlowItem({ text }) { const startTime = performance.now(); while (performance.now() - startTime < 1) { // 模拟慢组件(每个组件消耗 1ms) } return <li>Item: {text}</li>; }
-
useDeferredValue vs useTransition
| 特性 | useDeferredValue | useTransition |
|---|---|---|
| 用途 | 延迟某个值的更新 | 标记某个状态更新为低优先级 |
| 返回值 | 延迟的值 | [isPending, startTransition] |
| 控制粒度 | 值级别 | 更新级别 |
| 使用方式 | 包裹值 | 包裹 setState |
| 是否需要包裹函数 | ❌ 不需要 | ✅ 需要 startTransition |
| 适用场景 | 父组件传递给子组件的 props | 在组件内部控制状态更新 |
| 加载状态 | 需要手动对比判断 | 提供 isPending |
选择原则:
- 控制传递给子组件的值 → 用 useDeferredValue
- 控制自己的状态更新 → 用 useTransition
- 两者可以结合使用
示例对比
// ========== 使用 useDeferredValue ==========
function SearchWithDeferred() {
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query); // 延迟 query
return (
<>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<Results query={deferredQuery} /> {/* 传递延迟的值 */}
</>
);
}
// ========== 使用 useTransition ==========
function SearchWithTransition() {
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 = data.filter((item) => item.includes(value));
setResults(filtered);
});
};
return (
<>
<input value={query} onChange={handleChange} />
{isPending && <div>搜索中...</div>}
<Results data={results} />
</>
);
}
什么时候使用 useDeferredValue
| 场景 | 是否适用 | 原因 |
|---|---|---|
| 搜索框输入 | ✅ | 输入流畅,结果延迟更新 |
| 滑块拖动 | ✅ | 滑块流畅,图表延迟更新 |
| 过滤大列表 | ✅ | 避免阻塞输入 |
| 传递给子组件的 props | ✅ | 避免子组件频繁重新渲染 |
| 控制自己的状态更新 | ⚠️ | 用 useTransition 更合适 |
| 简单的状态更新 | ❌ | 没必要,过度优化 |
| 网络请求 | ❌ | 应该用其他方式(如 debounce) |
注意事项
-
⚠️ deferredValue 会"落后"于原始值
const [input, setInput] = useState(""); const deferredInput = useDeferredValue(input); // 用户输入 "hello" 时: // 1. input 立即变为 "h",deferredInput 还是 "" // 2. input 变为 "he",deferredInput 可能还是 "h" // 3. ...依此类推 // 可以用来判断是否正在延迟 const isStale = input !== deferredInput; -
⚠️ 延迟的值最终会同步
// React 确保最终 deferredValue 会等于 value // 但中间可能会跳过一些值 setValue("a"); setValue("b"); setValue("c"); // deferredValue 可能跳过 'b',直接从 'a' 到 'c' -
⚠️ 配合 memo 使用更有效
// ❌ 效果不明显:子组件每次都重新创建 function Parent() { const [text, setText] = useState(""); const deferredText = useDeferredValue(text); return <Child text={deferredText} />; // Child 会因为 Parent 渲染而渲染 } // ✅ 效果明显:子组件只在 deferredText 变化时渲染 function Parent() { const [text, setText] = useState(""); const deferredText = useDeferredValue(text); return <MemoChild text={deferredText} />; } const MemoChild = memo(function Child({ text }) { return <div>{text}</div>; }); -
⚠️ 不要用于对象或数组(引用会变化)
// ❌ 每次都是新对象,延迟无意义 const obj = { value: input }; const deferredObj = useDeferredValue(obj); // ✅ 使用基础类型或稳定的引用 const deferredInput = useDeferredValue(input); const deferredList = useDeferredValue(memoizedList); -
✅ 可以用来显示加载状态
const [query, setQuery] = useState(""); const deferredQuery = useDeferredValue(query); const isStale = query !== deferredQuery; return ( <div> <input value={query} onChange={(e) => setQuery(e.target.value)} /> {isStale && <Spinner />} {/* 显示加载指示器 */} <Results query={deferredQuery} /> </div> ); -
✅ 可以嵌套使用
const deferredA = useDeferredValue(a); const deferredB = useDeferredValue(deferredA); // deferredB 会比 deferredA 延迟更多
实用技巧
-
技巧 1:配合 useMemo 优化计算
function ExpensiveList({ filter }) { const deferredFilter = useDeferredValue(filter); // 只有 deferredFilter 变化时才重新计算 const filteredItems = useMemo(() => { return items.filter((item) => item.includes(deferredFilter)); }, [deferredFilter]); return ( <ul> {filteredItems.map((item) => ( <li key={item}>{item}</li> ))} </ul> ); } -
技巧 2:多级延迟
function MultiLevelDefer() { const [value, setValue] = useState(0); const deferred1 = useDeferredValue(value); const deferred2 = useDeferredValue(deferred1); return ( <div> <input type="number" value={value} onChange={(e) => setValue(Number(e.target.value))} /> <p>立即: {value}</p> <p>延迟1级: {deferred1}</p> <p>延迟2级: {deferred2}</p> </div> ); } -
技巧 3:条件性延迟
function ConditionalDefer({ shouldDefer, value }) { // 根据条件决定是否延迟 const displayValue = shouldDefer ? useDeferredValue(value) : value; return <ExpensiveComponent value={displayValue} />; }
与其他优化方案对比
| 方案 | 原理 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| useDeferredValue | 延迟值的更新 | 父传子的 props | 简单、React 原生 | 需要 React 18 |
| useTransition | 标记状态更新为低优先级 | 组件内状态更新 | 提供 isPending | 需要 React 18 |
| debounce | 延迟函数执行 | 减少 API 调用 | 灵活、通用 | 需要手动清理 |
| throttle | 限制函数执行频率 | 滚动、resize | 灵活、通用 | 需要手动清理 |
| memo | 避免不必要的渲染 | 纯组件优化 | 简单有效 | 只能优化组件层面 |
浏览器兼容性
- React 18+ 支持
- 需要现代浏览器支持(ES2015+)
- 不支持 IE 11
记住:useDeferredValue 返回一个"落后"的值,让紧急更新优先完成,延迟非紧急更新。非常适合优化需要传递给子组件的、计算密集的 props。