1.背景
useTransition
是 React 18 引入的一個并發模式下的 Hook,用于區分緊急和非緊急的狀態更新,提升應用的響應性和用戶體驗;- 它可以管理 UI 中的過渡狀態,特別是在處理長時間運行的狀態更新時。
- 它允許你將某些更新標記為“過渡”狀態,這樣 React 可以優先處理更重要的更新,比如用戶輸入,同時延遲處理過渡更新。
- 以下是其核心要點、使用場景和注意事項:
2.核心功能
- 標記過渡狀態:將非緊急的狀態更新標記為“過渡”(Transition),允許 React 延遲處理這些更新,優先處理高優先級任務(如用戶交互)。
- 保持 UI 響應:在狀態轉換期間,組件仍能保持響應,避免界面卡頓。
- 提供加載狀態:通過
isPending
指示過渡任務是否正在進行,可配合加載指示器(如 Spinner)提升用戶體驗。
3.基本用法
3.1 eg:
import { useTransition } from 'react';function MyComponent() {const [isPending, startTransition] = useTransition();const [count, setCount] = useState(0);const handleClick = () => {startTransition(() => {setCount(c => c + 1); // 標記為過渡更新});};return (<div>{isPending && <Spinner />} {/* 顯示加載指示器 */}<button onClick={handleClick}>Increment</button><p>Count: {count}</p></div>);
}
3.1 參數
useTransition
不需要任何參數
3.2 返回值
useTransition
返回一個數組 包含兩個元素[isPending, startTransition]
isPending
:布爾值,表示過渡任務是否正在進行。startTransition
:函數,用于包裹低優先級的狀態更新。
4、使用場景
4.1 大量數據渲染
例如,過濾或排序大型列表時,避免界面卡頓。
function DataGrid() {const [data, setData] = useState([]);const [isPending, startTransition] = useTransition();const [filter, setFilter] = useState('');const handleFilterChange = (newFilter) => {setFilter(newFilter);startTransition(() => {const filteredData = processLargeDataSet(newFilter);setData(filteredData);});};return (<div><input value={filter} onChange={e => handleFilterChange(e.target.value)} />{isPending ? <LoadingGrid /> : <VirtualizedGrid data={data} />}</div>);
}
4.2 路由切換:
預加載下一頁數據時保持當前頁響應。
function App() {const [isPending, startTransition] = useTransition();const [currentPage, setCurrentPage] = useState('home');const navigate = (page) => {startTransition(() => {setCurrentPage(page);});};return (<div><Navigation onNavigate={navigate} />{isPending ? <PageTransitionSpinner /> : <Page name={currentPage} />}</div>);
}
4.3 表單驗證
實時響應用戶輸入,同時延遲更新驗證結果。
function ComplexForm() {const [formData, setFormData] = useState({});const [errors, setErrors] = useState({});const [isPending, startTransition] = useTransition();const handleChange = (e) => {const { name, value } = e.target;setFormData(prev => ({ ...prev, [name]: value }));startTransition(() => {const validationErrors = validateFormField(name, value);setErrors(prev => ({ ...prev, [name]: validationErrors }));});};return (<form><input name="email" onChange={handleChange} value={formData.email || ''} />{isPending ? <ValidatingIndicator /> : (errors.email && <ErrorMessage error={errors.email} />)}</form>);
}
4.4 搜索或篩選功能
實時響應用戶輸入,同時延遲更新結果展示。
function SearchResults() {const [query, setQuery] = useState('');const [results, setResults] = useState([]);const [isPending, startTransition] = useTransition();const handleSearch = (e) => {setQuery(e.target.value);startTransition(() => {const searchResults = performSearch(e.target.value);setResults(searchResults);});};return (<div><input value={query} onChange={handleSearch} />{isPending ? <div>Loading...</div> : (<ul>{results.map(result => (<li key={result.id}>{result.title}</li>))}</ul>)}</div>);
}
5. 場景模擬
創建了一個簡單的輸入框和一個列表,用于展示基于輸入關鍵詞的結果。
mockjs文檔地址:https://github.com/nuysoft/Mock/wiki/Getting-Started
5.1 安裝使用到的插件
pnpm add mockjs antd
pnpm add -D @types/mockjs @type/node
package.json
{...."dependencies": {"antd": "^5.24.9","mockjs": "^1.1.0","react": "^19.0.0","react-dom": "^19.0.0"},"devDependencies": {"@types/mockjs": "^1.0.10","@types/node": "^22.15.3","@types/react": "^19.1.2",.....}
}
5.2 編寫 vite.config.ts
結合 vite插件實現一個api, 這個api可以幫助我們模擬數據。
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import type { Plugin } from 'vite'
import mockjs from 'mockjs'
// 使用 URL 模塊解析 URL
import url from 'node:url'/*** 創建一個 Vite Mock 服務器插件* @returns Vite 插件實例*/
const viteMockServer = (): Plugin => {return {// 插件名稱name: 'vite-plugin-mock',// 配置開發服務器configureServer(server) {// 添加中間件處理 mock 請求server.middlewares.use('/api/mock/list', (req, res) => {// 解析請求 URL 中的查詢參數const parseurl = url.parse(req.originalUrl ?? '', true).query;// 設置響應頭為 JSON 格式res.setHeader('Content-Type', 'application/json');// 使用 mockjs 生成模擬數據const data = mockjs.mock({'list|2000': [{id: '@guid',// name: '@cword(2, 4)',name: parseurl.key,age: '@integer(18, 30)',address: '@county(true)',},],});// 將數據轉換為 JSON 字符串并發送響應res.end(JSON.stringify(data));})}}
}// https://vite.dev/config/
export default defineConfig({plugins: [react(), viteMockServer()],
})
編寫完成訪問我們的接口
http://localhost:5174/api/list?keyWord=xx
5174為默認端口,可以自行更改,返回數據如下{"list": [{"id": "DCe1D11e-8D24-31e6-fE76-5AbAA6cf7E6F","name": "a","age": 25,"address": "西藏自治區 日喀則地區 仲巴縣"},{"id": "EAb5ffb6-b43B-93cC-cDfb-18A7Ce15BFda","name": "a","age": 21,"address": "安徽省 馬鞍山市 花山區"}.......] }
5.3 業務組件編寫
App.tsx
- 輸入框和狀態管理 使用 useState Hook 管理輸入框的值和結果列表。 每次輸入框的內容變化時,handleInputChange 函數會被觸發,它會獲取用戶輸入的值,并進行 API 請求。
- API 請求 在 handleInputChange 中,輸入的值會作為查詢參數發送到 /api/list API。API 返回的數據用于更新結果列表。 為了優化用戶體驗,我們將結果更新放在 startTransition 函數中,這樣 React 可以在處理更新時保持輸入框的響應性。
- 使用 useTransition useTransition 返回一個布爾值 isPending,指示過渡任務是否仍在進行中。 當用戶輸入時,如果正在加載數據,我們會顯示一個簡單的“loading…”提示,以告知用戶當前操作仍在進行。
- 列表渲染 使用 List 組件展示返回的結果,列表項顯示每個結果的 name 和 address。
// 使用 React 和 Ant Design 組件實現一個模擬帶搜索功能的列表
import { Input, List, Spin } from 'antd'
import { useState, useTransition, useCallback } from 'react'interface ListItem {id: string;name: string;address: string;age: number;
}function App() {const [list, setList] = useState<ListItem[]>([]);const [inputValue, setInputValue] = useState('');const [isPending, startTransition] = useTransition();// 處理輸入框變化,使用 useCallback 優化性能const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {const value = e.target.value;setInputValue(value);// 發起請求獲取數據fetch(`/api/mock/list?key=${value}`).then((res) => res.json()).then((res) => {// 使用 startTransition 包裹狀態更新,優化大數據渲染性能startTransition(() => {setList(res.list)});}).catch(err => {console.error('獲取數據失敗:', err);});}, []);return (<><Input placeholder="請輸入搜索關鍵詞"value={inputValue} onChange={handleInputChange} />{isPending ? (<Spin tip="加載中..." />) : (<ListdataSource={list}renderItem={(item) => (<List.Item>{item.address}</List.Item>)}/>)}</>);
}export default App;
5.4 測試使用
為了更好的測試結果可以在性能中降級 cpu 渲染速度
6. 注意事項
startTransition必須是同步的
錯誤做法
startTransition(() => {// ? 在調用 startTransition 后更新狀態setTimeout(() => {setPage('/about');}, 1000);
});
正確做法
setTimeout(() => {startTransition(() => {// ? 在調用 startTransition 中更新狀態setPage('/about');});
}, 1000);
async await 錯誤做法
startTransition(async () => {await someAsyncFunction();// ? 在調用 startTransition 后更新狀態setPage('/about');
});
正確做法
await someAsyncFunction();
startTransition(() => {// ? 在調用 startTransition 中更新狀態setPage('/about');
});
7. 原理剖析
useTransition 的核心原理是將一部分狀態更新處理為低優先級任務,這樣可以將關鍵的高優先級任務先執行,而低優先級的過渡更新則會稍微延遲處理。這在渲染大量數據、進行復雜運算或處理長時間任務時特別有效。React 通過調度機制來管理優先級:
- 高優先級更新:直接影響用戶體驗的任務,比如表單輸入、按鈕點擊等。
- 低優先級更新:相對不影響交互的過渡性任務,比如大量數據渲染、動畫等,這些任務可以延遲執行。
+-----------------------+| App || || +--------------+ || | Input | || +--------------+ || || +--------------+ || | Display | || +--------------+ |+-----------------------+用戶輸入|v[高優先級更新] ---> [調度器] ---> [React 更新組件]|+---> [低優先級過渡更新] --> [調度器] --> [等待處理]
8.擴展
useTransition
與防抖的區別?
seTransition
與 防抖在前端開發中都是用于優化性能的手段,但它們的核心原理、應用場景和實現方式存在顯著區別,以下是詳細對比:
核心原理對比
useTransition
- 基于React的并發模式(Concurrent Features),通過將狀態更新標記為“過渡”(Transition)實現。
- 允許React在后臺處理低優先級任務,優先響應高優先級操作(如用戶輸入),從而保持界面流暢。
- 更新過程可中斷,避免長時間阻塞主線程,提升用戶體驗。
- 防抖(Debounce)
- 延遲函數執行,直到事件觸發后的一段時間內沒有再次觸發。
- 適用于需要等待用戶停止操作后再執行的場景(如搜索輸入、表單驗證)。
應用場景對比
useTransition
適用場景- 大數據列表過濾:當用戶輸入搜索內容時,通過
useTransition
將篩選任務標記為低優先級,避免阻塞輸入框的實時更新,保持輸入流暢性。 - 復雜UI更新:在渲染需要消耗大量時間的頁面時,使用
useTransition
優化視圖切換體驗。 - 表單提交與篩選器切換:需要延遲渲染的操作(如異步數據加載)中,
useTransition
可確保用戶交互的即時響應。
- 大數據列表過濾:當用戶輸入搜索內容時,通過
- 防抖適用場景
- 搜索框輸入檢測:等待用戶停止輸入后再執行搜索,減少無效請求。
- 手機號和郵箱驗證:在用戶停止輸入后驗證格式,避免實時驗證的性能開銷。
- 窗口大小變化后的重新渲染:在窗口調整結束后執行布局計算,避免頻繁重繪。
優缺點對比
useTransition
優點- 更新協調過程可中斷,避免長時間阻塞主線程。
- 用戶操作可及時得到響應,提升交互體驗。
- 不需要開發者手動配置時間間隔,React自動優化。
useTransition
缺點- 僅適用于React 18及以上版本,且需配合并發模式使用。
- 對于簡單場景,可能引入不必要的復雜性。
- 防抖優點
- 可確保在用戶停止操作后執行函數,減少無效請求。
- 實現簡單,適用于等待用戶輸入的場景。
- 防抖缺點
- 可能導致用戶輸入長時間得不到響應。
- 無法處理需要即時反饋的操作。