適用場景
- 提高UX: 當執行耗時的狀態更新時,例如過濾大型資料或渲染大量資料時,
useTransition
可以將這些更新標記為低優先級,確保用戶的互動(如輸入、點擊)保持流暢。 - 提高性能: 當進行狀態更新可能導致大量 DOM 變化時,例如切換頁面、加載資料等,
useTransition
可以讓瀏覽器有更多時間處理高優先級的操作。 - 分離緊急與非緊急更新: 高優先級更新(如輸入框內容變更)與低優先級更新(如篩選數據顯示結果)可以使用
useTransition
分開處理,避免阻塞。
場景一:搜尋過濾
- 用戶輸入時,
query
的更新是高優先級的,即時反饋到輸入框。 - 過濾資料是低優先級的,使用
startTransition
延後處理,避免阻塞用戶的輸入。
import React, { useState, useTransition } from 'react';
function App() {
const [query, setQuery] = useState(''); // 高優先級
const [filteredItems, setFilteredItems] = useState([]); // 低優先級
const [isPending, startTransition] = useTransition();
const items = Array.from({ length: 10000 }, (_, i) => `Item ${i + 1}`);
const handleInputChange = (event) => {
const value = event.target.value;
setQuery(value); // 即時更新輸入框(高優先級)
startTransition(() => {
const result = items.filter((item) =>
item.toLowerCase().includes(value.toLowerCase())
);
setFilteredItems(result); // 篩選操作(低優先級)
});
};
return (
<div>
<input
type="text"
value={query}
onChange={handleInputChange}
placeholder="Search..."
/>
{isPending && <p>Loading...</p>}
<ul>
{filteredItems.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
export default App;
場景二:切換頁面
假設有一個按鈕可以切換不同的list頁面,每次切換都會渲染大量資料。使用 useTransition
可以讓按鈕的狀態改變(如顯示 “Loading…”)先於資料渲染,提升UX。
- 點擊按鈕切換頁面時,按鈕的狀態(如 disabled)會立即更新。
- 數據的渲染被標記為低優先級,等按鈕狀態更新完成後才進行。
import React, { useState, useTransition } from "react";
function App() {
const [currentPage, setCurrentPage] = useState(1);
const [data, setData] = useState([]);
const [isPending, startTransition] = useTransition();
const fetchData = (page) => {
return Array.from({ length: 5000 }, (_, i) => `Page ${page} - Item ${i + 1}`);
};
const handlePageChange = (page) => {
startTransition(() => {
setCurrentPage(page);
const newData = fetchData(page);
setData(newData); // 更新大數據渲染為低優先級
});
};
return (
<div>
<div>
<button onClick={() => handlePageChange(1)} disabled={isPending}>
Page 1
</button>
<button onClick={() => handlePageChange(2)} disabled={isPending}>
Page 2
</button>
{isPending && <p>Loading data...</p>}
</div>
<ul>
{data.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
export default App;
場景三:UI 更新
在動態切換顏色或樣式的情境下,UI 更新可能會導致卡頓。可以用 useTransition
將樣式變化標記為低優先級。
import React, { useState, useTransition } from "react";
function App() {
const [color, setColor] = useState("red");
const [boxes, setBoxes] = useState([]);
const [isPending, startTransition] = useTransition();
const changeColor = (newColor) => {
startTransition(() => {
setColor(newColor); // 延遲樣式更新
const newBoxes = Array.from({ length: 10000 }, (_, i) => ({
id: i,
color: newColor,
}));
setBoxes(newBoxes);
});
};
return (
<div>
<button onClick={() => changeColor("red")} disabled={isPending}>
Red
</button>
<button onClick={() => changeColor("blue")} disabled={isPending}>
Blue
</button>
<div style={{ display: "flex", flexWrap: "wrap" }}>
{boxes.map((box) => (
<div
key={box.id}
style={{
width: 10,
height: 10,
backgroundColor: box.color,
margin: 1,
}}
/>
))}
</div>
{isPending && <p>Updating...</p>}
</div>
);
}
export default App;
場景四:滾動加載
透過 useTransition
,可以在用戶滾動時,逐步載入未顯示的分頁。
- 清單數據量很大,但只有滾動到底部時才載入更多,減少初次渲染的時間。
useTransition
可以確保滾動流暢,用戶不會感覺卡頓。
import React, { useState, useEffect, useTransition } from 'react';
function App() {
const [currentPage, setCurrentPage] = useState(0); // 當前分頁
const [visibleData, setVisibleData] = useState([]); // 已載入的資料
const [isPending, startTransition] = useTransition();
const pageSize = 100; // 每頁顯示的數量
const totalData = Array.from({ length: 10000 }, (_, i) => `Item ${i + 1}`);
useEffect(() => {
setVisibleData(totalData.slice(0, pageSize));
}, []);
const loadMoreData = () => {
if (currentPage * pageSize >= totalData.length) return;
startTransition(() => {
const nextPage = currentPage + 1;
const nextData = totalData.slice(0, nextPage * pageSize);
setVisibleData(nextData);
setCurrentPage(nextPage);
});
};
return (
<div>
<div
style={{ height: '300px', overflow: 'auto', border: '1px solid black' }}
onScroll={(e) => {
const { scrollTop, scrollHeight, clientHeight } = e.target;
if (scrollTop + clientHeight >= scrollHeight - 20) {
loadMoreData(); // 滾動到底部時載入更多
}
}}
>
<ul>
{visibleData.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
{isPending && <p>Loading more data...</p>}
</div>
);
}
export default App;
場景五:動態排序和動畫過渡
對大型列表進行排序時,會觸發大量 DOM 更新,可能導致頁面卡頓。透過 useTransition
,可以在排序過程中加入動畫過渡,顯得更流暢。
- 運用動畫讓排序過渡更自然。
- 適合排序或重新排列需要渲染大量項目的場景。
import React, { useState, useTransition } from 'react';
function App() {
const [items, setItems] = useState(
Array.from({ length: 50 }, (_, i) => ({ id: i, value: Math.random() }))
);
const [isPending, startTransition] = useTransition();
const sortItems = () => {
startTransition(() => {
const sorted = [...items].sort((a, b) => a.value - b.value); // 排序資料
setItems(sorted); // 更新為低優先級操作
});
};
return (
<div>
<button onClick={sortItems} disabled={isPending}>
Sort
</button>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: '10px' }}>
{items.map((item) => (
<div
key={item.id}
style={{
height: '50px',
background: 'skyblue',
transition: 'transform 0.3s ease',
transform: `translateY(${Math.random() * 10}px)`
}}
>
{item.value.toFixed(2)}
</div>
))}
</div>
{isPending && <p>Sorting...</p>}
</div>
);
}
export default App;
場景六:主題切換
在應用程式中切換主題時,涉及大量的樣式更新,可能會導致整個頁面閃爍。useTransition
可以確保主題切換過程更加流暢。
- 將主題更新標記為低優先級,確保用戶的其他操作(如點擊或滾動)不會被打斷。
import React, { useState, useTransition } from 'react';
function App() {
const [theme, setTheme] = useState('light');
const [isPending, startTransition] = useTransition();
const toggleTheme = () => {
startTransition(() => {
setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));
});
};
return (
<div
style={{
background: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#000' : '#fff',
minHeight: '100vh',
transition: 'all 0.5s ease'
}}
>
<button onClick={toggleTheme} disabled={isPending}>
Toggle Theme
</button>
<p>Current theme: {theme}</p>
</div>
);
}
export default App;
小結
useTransition
的主要作用是將耗時操作標記為低優先級,從而避免用戶界面卡頓。
使用 useTransition
時,關鍵是區分哪些是高優先級的緊急操作(例如用戶輸入、點擊)和低優先級的非緊急操作(例如渲染或數據更新)。