useTransition 与 useDeferredValue 一、React 18 并发特性 1.1 什么是并发渲染? 并发渲染允许 React 在渲染过程中中断、暂停、恢复或放弃渲染,从而保持 UI 响应性。
1.2 两个核心 Hook Hook 用途 适用场景 useTransition 标记非紧急更新 页面切换、搜索过滤 useDeferredValue 延迟更新某个值 输入框实时搜索
二、useTransition 2.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 type="text" 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 className="loading-overlay"> <Spinner /> </div> )} <Suspense fallback={<PageSkeleton />}> {page === 'home' && <HomePage />} {page === 'about' && <AboutPage />} {page === 'contact' && <ContactPage />} </Suspense> </div> ); }三、useDeferredValue 3.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 type="text" value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} placeholder="搜索产品..." /> {/* 视觉反馈 */} <div> 搜索: {searchTerm} {deferredSearchTerm !== searchTerm && " (更新中...)"} </div> <div className="product-grid"> {filteredProducts.map(product => ( <ProductCard key={product.id} product={product} /> ))} </div> </div> ); }四、useTransition vs useDeferredValue 4.1 区别对比 特性 useTransition useDeferredValue 控制方式 主动标记更新 被动延迟值 返回值 [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 className="sorting-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 className="controls"> <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 className="autocomplete"> <input value={input} onChange={(e) => setInput(e.target.value)} placeholder="输入内容..." /> {input !== deferredInput && <div className="typing-indicator">输入中...</div>} {isLoading ? ( <div>加载建议...</div> ) : suggestions.length > 0 && ( <ul className="suggestions"> {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 忘记处理 isPending function 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 className="tabs"> {tabs.map(t => ( <button key={t} onClick={() => handleTabChange(t)} className={tab === t ? 'active' : ''} > {t} </button> ))} </div> {isPending && <div className="loading">加载中...</div>} <div className="tab-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) 不要过度使用,简单更新不需要