關於useTransition
Created: 2024/12/12
Updated: 2024/12/12
紀錄react 18中關於useTransition
useTransition 這個新的hook在React 18引入,主要用來幫助管理可能阻塞 UI 的狀態更新。允許將某些更新標記為"過渡",這些更新可以被更緊急的更新所打斷。
這可以通過使 UI 更加響應來改善用戶體驗,即使在處理緩慢或複雜的狀態更新時。
useTransition 返回一個包含兩個元素的陣列:
isPending:一個布林值,表示當前是否有過渡正在進行中。startTransition:一個函數,用來包裝應被視為過渡的狀態更新。
const [isPending, startTransition] = useTransition();基本用法和例子
當你希望在不阻塞 UI 的情況下更新狀態時,可以將狀態更新包裝在 startTransition 函數內。以下是一個基本例子:
import React, { useState, useTransition } from 'react';
import './App.css';
// 渲染大量組件
function HeavyList({ query }) {
const items = [];
// 創建大量 DOM 元素
for (let i = 0; i < 20000; i++) {
if (query === '' || i.toString().includes(query)) {
items.push(
<div key={i}>
Item {i} - {query}
</div>
);
}
}
return <div>{items}</div>;
}
function SearchWithTransition() {
const [isPending, startTransition] = useTransition();
const [input, setInput] = useState('');
const [query, setQuery] = useState('');
const handleChange = (e) => {
const value = e.target.value;
setInput(value); // 立即更新輸入框
startTransition(() => {
setQuery(value); // 延遲更新渲染
});
};
return (
<div>
<h3>使用 useTransition</h3>
<input type="text" value={input} onChange={handleChange} />
{isPending && <p style={{ color: 'green' }}>🔄 更新中...</p>}
<div
style={{
maxHeight: '400px',
overflow: 'auto',
border: '1px solid #ccc',
}}
>
<HeavyList query={query} />
</div>
</div>
);
}
function SearchWithoutTransition() {
const [input, setInput] = useState('');
const handleChange = (e) => {
const value = e.target.value;
setInput(value); // 直接更新,會導致立即重新渲染
};
return (
<div>
<h3>不使用 useTransition</h3>
<input type="text" value={input} onChange={handleChange} />
<div
style={{
maxHeight: '400px',
overflow: 'auto',
border: '1px solid #ccc',
}}
>
<HeavyList query={input} />
</div>
</div>
);
}
function App() {
return (
<div>
<h1>useTransition 對比</h1>
<p>
<strong>測試方法:在兩個輸入框中快速連續打字(例如:12345)</strong>
</p>
<div style={{ display: 'flex', gap: '20px' }}>
<div style={{ flex: 1 }}>
<SearchWithTransition />
</div>
<div style={{ flex: 1 }}>
<SearchWithoutTransition />
</div>
</div>
</div>
);
}
export default App;
傳遞給startTransition的函數,能進行更新狀態和執行effect操作。並且不會阻塞用戶的交互。
通過將狀態更新標記為過渡,來確保更緊急的更新(如用戶輸入)優先處理。這有助於保持 UI 的響應性。
在這個例子中,輸入框中的輸入操作不會被狀態更新所阻塞(測試時,可以把devtools Performance的cpu效能調低)。相反,狀態更新將被視為過渡來處理,確保輸入保持響應。
注意事項
startTransition必須要是同步的。
錯誤示範:
// ❌ 錯誤:在 startTransition 內使用異步操作
startTransition(() => {
setTimeout(() => {
setState(newState);
}, 1000);
});正確做法:
// ✅ 正確:異步操作在外部,只將狀態更新包裝在 transition 中
setTimeout(() => {
startTransition(() => {
setState(newState);
});
}, 1000);錯誤示範:
startTransition(async () => {
await someAsyncFunction();
// ❌ 錯誤:在呼叫 startTransition 後更新
setPage('/about');
});正確做法:
await someAsyncFunction();
startTransition(() => {
// ✅ 正確:在呼叫 startTransition 後更新
setPage('/about');
});原理
useTransition是將一部份的狀態更新處理標記成較低悠閒及任務。讓低優先級的過度更新延遲去做,先去做高優先級任務。
像是表單輸入、按鈕點擊這些高優先擊任務會先更新,而相對不影響相互的過渡性任務,像是大量數據渲染或者動畫,可以延遲執行。
用戶操作(如輸入)
|
v
React 接收狀態更新,優先級判定
|
|
+---> 是否在 startTransition 內?
| |
| +---> 是 ---> 標記為低優先級
| +---> 否 ---> 標記為高優先級
v
Scheduler 調度器
|
+---> 高優先級任務(立即執行)
| |
| +---> 更新 DOM
| +---> 用戶看到即時反饋
|
+---> 低優先級任務(延遲執行)
|
+---> 等待高優先級任務完成
+---> 在瀏覽器空閒時執行
+---> 可被新的高優先級任務中斷
關鍵要點:
useTransition用於標記低優先級的狀態更新- 始終保持緊急更新(如輸入框)同步進行
- 不要在
startTransition內部直接使用異步操作 - 使用
isPending提供視覺反饋,改善用戶體驗 - 與
Suspense配合使用可避免不必要的加載指示器