02-Hooks完全指南——08-useTransition 与 useDeferredValue
useTransition 与 useDeferredValue一、React 18 并发特性1.1 什么是并发渲染并发渲染允许 React 在渲染过程中中断、暂停、恢复或放弃渲染从而保持 UI 响应性。1.2 两个核心 HookHook用途适用场景useTransition标记非紧急更新页面切换、搜索过滤useDeferredValue延迟更新某个值输入框实时搜索二、useTransition2.1 基本语法const [isPending, startTransition] useTransition();isPending布尔值表示是否有进行中的 transitionstartTransition将更新标记为非紧急的函数2.2 基础示例function TabSwitcher() { const [tab, setTab] useState(home); const [isPending, startTransition] useTransition(); const handleTabChange (newTab) { // 标记为非紧急更新 startTransition(() { setTab(newTab); }); }; return ( div button onClick{() handleTabChange(home)}首页/button button onClick{() handleTabChange(profile)}个人资料/button button onClick{() handleTabChange(settings)}设置/button {isPending div加载中.../div} div {tab home HomeTab /} {tab profile ProfileTab /} {tab settings SettingsTab /} /div /div ); }2.3 搜索过滤示例function SearchPage() { const [query, setQuery] useState(); const [filteredList, setFilteredList] useState([]); const [isPending, startTransition] useTransition(); const allItems Array.from({ length: 10000 }, (_, i) 项目 ${i}); const handleSearch (e) { const value e.target.value; setQuery(value); // 过滤操作标记为低优先级 startTransition(() { const filtered allItems.filter(item item.toLowerCase().includes(value.toLowerCase()) ); setFilteredList(filtered); }); }; return ( div input typetext value{query} onChange{handleSearch} placeholder搜索... / {isPending div正在搜索.../div} ul {filteredList.map(item ( li key{item}{item}/li ))} /ul /div ); }2.4 路由切换优化function Router() { const [page, setPage] useState(home); const [isPending, startTransition] useTransition(); const navigate (newPage) { startTransition(() { setPage(newPage); }); }; return ( div nav button onClick{() navigate(home)}首页/button button onClick{() navigate(about)}关于/button button onClick{() navigate(contact)}联系/button /nav {isPending ( div classNameloading-overlay Spinner / /div )} Suspense fallback{PageSkeleton /} {page home HomePage /} {page about AboutPage /} {page contact ContactPage /} /Suspense /div ); }三、useDeferredValue3.1 基本语法const deferredValue useDeferredValue(value);返回一个延迟更新的值在紧急更新完成后才会更新3.2 基础示例function SearchWithDeferred() { const [query, setQuery] useState(); const deferredQuery useDeferredValue(query); // 使用延迟的 query 进行搜索 const results useMemo(() { return performExpensiveSearch(deferredQuery); }, [deferredQuery]); return ( div input value{query} onChange{(e) setQuery(e.target.value)} placeholder输入搜索... / {/* 显示提示 */} {deferredQuery ! query div正在更新列表.../div} List results{results} / /div ); }3.3 实时搜索优化function RealTimeSearch() { const [searchTerm, setSearchTerm] useState(); const deferredSearchTerm useDeferredValue(searchTerm); // 昂贵的过滤操作 const filteredProducts useMemo(() { console.log(过滤产品...); return products.filter(product product.name.toLowerCase().includes(deferredSearchTerm.toLowerCase()) ); }, [deferredSearchTerm]); return ( div input typetext value{searchTerm} onChange{(e) setSearchTerm(e.target.value)} placeholder搜索产品... / {/* 视觉反馈 */} div 搜索: {searchTerm} {deferredSearchTerm ! searchTerm (更新中...)} /div div classNameproduct-grid {filteredProducts.map(product ( ProductCard key{product.id} product{product} / ))} /div /div ); }四、useTransition vs useDeferredValue4.1 区别对比特性useTransitionuseDeferredValue控制方式主动标记更新被动延迟值返回值[isPending, startTransition]deferredValue适用场景可控制的更新接收外部传入的值加载指示有isPending需要手动比较4.2 选择建议// 场景1可以控制更新时使用 useTransition function ControlledSearch() { const [query, setQuery] useState(); const [isPending, startTransition] useTransition(); const handleChange (e) { const value e.target.value; setQuery(value); startTransition(() { // 执行搜索 }); }; } // 场景2接收 props 或无法控制时使用 useDeferredValue function UncontrolledSearch({ searchTerm }) { const deferredTerm useDeferredValue(searchTerm); // 使用 deferredTerm 进行搜索 }五、实战案例5.1 大数据表格渲染function LargeDataTable({ data }) { const [sortField, setSortField] useState(id); const [sortDirection, setSortDirection] useState(asc); const [isPending, startTransition] useTransition(); const handleSort (field) { startTransition(() { setSortField(field); setSortDirection(prev field sortField prev asc ? desc : asc ); }); }; const sortedData useMemo(() { return [...data].sort((a, b) { const aVal a[sortField]; const bVal b[sortField]; if (sortDirection asc) { return aVal bVal ? 1 : -1; } return aVal bVal ? 1 : -1; }); }, [data, sortField, sortDirection]); return ( div {isPending div classNamesorting-indicator排序中.../div} table thead tr th onClick{() handleSort(id)}ID/th th onClick{() handleSort(name)}姓名/th th onClick{() handleSort(age)}年龄/th /tr /thead tbody {sortedData.map(row ( tr key{row.id} td{row.id}/td td{row.name}/td td{row.age}/td /tr ))} /tbody /table /div ); }5.2 仪表盘数据刷新function Dashboard() { const [timeRange, setTimeRange] useState(day); const [isPending, startTransition] useTransition(); const handleTimeRangeChange (range) { // 立即更新 UI 显示 setTimeRange(range); // 延迟数据获取 startTransition(() { fetchDashboardData(range); }); }; return ( div div classNamecontrols button onClick{() handleTimeRangeChange(day)}日/button button onClick{() handleTimeRangeChange(week)}周/button button onClick{() handleTimeRangeChange(month)}月/button /div {isPending SkeletonLoader /} DashboardContent / /div ); }5.3 自动完成输入框function Autocomplete() { const [input, setInput] useState(); const deferredInput useDeferredValue(input); const [suggestions, setSuggestions] useState([]); const [isLoading, setIsLoading] useState(false); useEffect(() { if (!deferredInput) { setSuggestions([]); return; } setIsLoading(true); fetch(/api/suggestions?q${deferredInput}) .then(res res.json()) .then(data { setSuggestions(data); setIsLoading(false); }); }, [deferredInput]); return ( div classNameautocomplete input value{input} onChange{(e) setInput(e.target.value)} placeholder输入内容... / {input ! deferredInput div classNametyping-indicator输入中.../div} {isLoading ? ( div加载建议.../div ) : suggestions.length 0 ( ul classNamesuggestions {suggestions.map(suggestion ( li key{suggestion.id}{suggestion.text}/li ))} /ul )} /div ); }六、性能优化技巧6.1 结合 useMemo 使用function OptimizedSearch() { const [query, setQuery] useState(); const deferredQuery useDeferredValue(query); // ✅ 使用 useMemo 缓存计算结果 const results useMemo(() { return expensiveSearch(deferredQuery); }, [deferredQuery]); return ( div input value{query} onChange{(e) setQuery(e.target.value)} / Results data{results} / /div ); }6.2 使用 startTransition 包装function BetterTransition() { const [isPending, startTransition] useTransition(); const handleUpdate () { // 紧急更新更新 UI 状态 setUiState(loading); // 非紧急更新数据处理 startTransition(() { setData(processLargeData()); }); }; }七、常见陷阱7.1 不必要的 useTransition// ❌ 简单更新不需要 useTransition const [isPending, startTransition] useTransition(); startTransition(() { setCount(count 1); // 太简单不需要 }); // ✅ 只对昂贵的更新使用 startTransition(() { setFilteredList(expensiveFilter(allItems, query)); });7.2 忘记处理 isPendingfunction MissingPending() { const [isPending, startTransition] useTransition(); const [data, setData] useState([]); const handleSearch (query) { startTransition(() { setData(searchData(query)); }); }; // ❌ 用户不知道正在更新 return div{/* 没有加载指示器 */}/div; } // ✅ 提供视觉反馈 function WithPending() { const [isPending, startTransition] useTransition(); return ( div {isPending Spinner /} {/* 内容 */} /div ); }八、练习题基础题实现一个标签页切换使用 useTransition 优化实现一个搜索框使用 useDeferredValue 优化进阶题实现一个带自动完成功能的搜索框实现一个大表格的排序功能使用 useTransition参考答案// 标签页切换优化 function OptimizedTabs() { const [tab, setTab] useState(home); const [isPending, startTransition] useTransition(); const tabs [home, products, about, contact]; const handleTabChange (newTab) { startTransition(() { setTab(newTab); }); }; return ( div div classNametabs {tabs.map(t ( button key{t} onClick{() handleTabChange(t)} className{tab t ? active : } {t} /button ))} /div {isPending div classNameloading加载中.../div} div classNametab-content Suspense fallback{div加载中.../div} {tab home Home /} {tab products Products /} {tab about About /} {tab contact Contact /} /Suspense /div /div ); }九、小结要点说明useTransition标记非紧急更新保持 UI 响应useDeferredValue延迟更新值用于外部传入适用场景搜索过滤、表格排序、路由切换用户体验提供 isPending 视觉反馈核心要点useTransition 和 useDeferredValue 是并发渲染的核心 API将昂贵的更新标记为低优先级始终提供视觉反馈isPending不要过度使用简单更新不需要