React 的 useTransition:構建高性能搜索的五萬條記錄案例研究
在構建現(xiàn)代 Web 應用時,確保流暢的用戶交互至關重要。React 憑借其強大的聲明式 UI 更新和基于組件的架構,提供了極大的靈活性。然而,隨著應用復雜度的增加,性能可能會下降,尤其是在處理大數(shù)據(jù)集或密集用戶交互時。本文將重點介紹我們在 React 中實現(xiàn)簡單搜索過濾器時遇到的挑戰(zhàn),以及如何通過優(yōu)化組件來克服性能問題。
問題:與大數(shù)據(jù)集的用戶交互
想象一個應用,它顯示一個龐大的用戶列表,用戶可以通過在搜索框中輸入來過濾該列表。乍一看,這似乎是一個簡單的任務,但隨著用戶數(shù)量的增加,即使是很小的低效也會導致嚴重的性能問題。我們最初構建了一個搜索過濾器組件,允許用戶通過姓名或電子郵件過濾 5,000 名用戶的列表。交互包括在輸入字段中輸入,并在用戶輸入時動態(tài)過濾列表。然而,性能很快就變得有問題。
原始方法
這是我們組件的第一個版本:
import React, { useState, useTransition } from"react";
// ?? 假用戶生成器
constgenerateUsers = (count: number) => {
const names = ["Alice", "Bob", "Charlie", "David", "Eve", "Frank", "Grace", "Helen"];
returnArray.from({ length: count }, (_, i) => ({
id: i,
name: `${names[i % names.length]} ${i}`,
email: `user${i}@example.com`,
}));
};
const usersData = generateUsers(5000);
exportdefaultfunctionMassiveSearchFilter() {
const [query, setQuery] = useState("");
const [filteredUsers, setFilteredUsers] = useState(usersData);
const [isPending, startTransition] = useTransition();
consthandleSearch = (e: { target: { value: any; }; }) => {
const value = e.target.value;
setQuery(value);
startTransition(() => {
const lower = value.toLowerCase();
const filtered = usersData.filter(
(user) =>
user.name.toLowerCase().includes(lower) ||
user.email.toLowerCase().includes(lower)
);
setFilteredUsers(filtered);
});
};
consthighlight = (text: string, query: string) => {
if (!query) return text;
const index = text.toLowerCase().indexOf(query.toLowerCase());
if (index === -1) return text;
return (
<>
{text.slice(0, index)}
<mark className="bg-pink-200 text-black">
{text.slice(index, index + query.length)}
</mark>
{text.slice(index + query.length)}
</>
);
};
return (
<div className="min-h-screen bg-gray-100 p-6 font-sans">
<div className="max-w-3xl mx-auto bg-white p-6 rounded-2xl border-2 border-pink-200 shadow-md">
<h1 className="text-2xl font-bold text-pink-600 mb-4">
???? 大規(guī)模搜索過濾器(5,000 用戶)
</h1>
<input
type="text"
value={query}
onChange={handleSearch}
placeholder="搜索姓名或電子郵件..."
className="w-full p-3 mb-4 border-2 border-pink-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-pink-400"
/>
{isPending && (
<div className="text-pink-500 italic mb-2 text-sm animate-pulse">
?? 加載結果中...
</div>
)}
<div className="max-h-[500px] overflow-y-auto divide-y divide-pink-100 border-t border-pink-100">
{filteredUsers.map((user) => (
<div
key={user.id}
className="p-3 hover:bg-pink-50 transition-colors duration-150"
>
<p className="font-medium text-gray-800 text-base">
{highlight(user.name, query)}
</p>
<p className="text-sm text-gray-500">
{highlight(user.email, query)}
</p>
</div>
))}
</div>
</div>
</div>
);
}
面臨的挑戰(zhàn)
起初,一切似乎都很順利。搜索速度很快,結果幾乎瞬間出現(xiàn)。然而,當我們開始使用更大的數(shù)據(jù)集(例如 5,000 多名用戶)進行測試時,以下問題很快就變得明顯:
- 性能瓶頸:在搜索字段中快速輸入時,組件會顯著變慢。React 會在每次按鍵時重新渲染整個 5,000 名用戶的列表,導致 UI 更新延遲。
- UI 凍結:由于搜索過濾器直接與輸入字段和用戶交互綁定,快速輸入或刪除文本會導致 UI 凍結,因為 React 被過濾和重新渲染數(shù)千個 DOM 節(jié)點的任務壓垮了。
- 過度依賴過濾:搜索輸入中的每次更改都會觸發(fā)對整個列表的過濾,即使用戶仍在輸入,這也是低效的。
優(yōu)化過程
在識別出問題后,我們應用了幾種技術來提高性能和用戶體驗:
步驟 1:使用 react-window 進行虛擬化
渲染 5,000 多個項目是虛擬化的經典案例。我們決定使用 react-window,它允許 React 僅渲染視口中可見的項目。這意味著即使在大數(shù)據(jù)集的情況下,我們也不需要渲染整個列表,從而大大減少了瀏覽器的負載。
步驟 2:過濾結果的記憶化
通過使用 useMemo 鉤子,我們根據(jù)查詢對過濾結果進行了記憶化。這確保了過濾計算僅在查詢更改時發(fā)生,而不是在每次渲染時發(fā)生。
步驟 3:使用 startTransition 實現(xiàn)平滑的 UI 更新
useTransition 鉤子允許我們優(yōu)先更新輸入字段,同時推遲過濾計算。這確保了 UI 在應用執(zhí)行更昂貴的過濾任務時仍保持響應。
優(yōu)化后的組件
以下是經過這些更改后的優(yōu)化版本組件:
import React, { useState, useMemo, useTransition } from"react";
import { FixedSizeListasList } from"react-window";
constgenerateUsers = (count: number) => {
const names = ["Alice", "Bob", "Charlie", "David", "Eve", "Frank", "Grace", "Helen"];
returnArray.from({ length: count }, (_, i) => ({
id: i,
name: `${names[i % names.length]} ${i}`,
email: `user${i}@example.com`,
}));
};
const usersData = generateUsers(5000);
exportdefaultfunctionMassiveSearchFilter() {
const [query, setQuery] = useState("");
const [isPending, startTransition] = useTransition();
const filteredUsers = useMemo(() => {
const lower = query.toLowerCase();
return usersData.filter(
(user) =>
user.name.toLowerCase().includes(lower) ||
user.email.toLowerCase().includes(lower)
);
}, [query]);
consthandleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
startTransition(() =>setQuery(value));
};
consthighlight = (text: string, query: string) => {
if (!query) return text;
const index = text.toLowerCase().indexOf(query.toLowerCase());
if (index === -1) return text;
return (
<>
{text.slice(0, index)}
<mark className="bg-pink-200 text-black">
{text.slice(index, index + query.length)}
</mark>
{text.slice(index + query.length)}
</>
);
};
constRow = ({ index, style }: { index: number; style: React.CSSProperties }) => {
const user = filteredUsers[index];
return (
<div
style={style}
key={user.id}
className="p-3 border-b border-pink-100 hover:bg-pink-50"
>
<p className="font-medium text-gray-800 text-base">
{highlight(user.name, query)}
</p>
<p className="text-sm text-gray-500">{highlight(user.email, query)}</p>
</div>
);
};
return (
<div className="min-h-screen bg-gray-100 p-6 font-sans">
<div className="max-w-3xl mx-auto bg-white p-6 rounded-2xl border-2 border-pink-200 shadow-md">
<h1 className="text-2xl font-bold text-pink-600 mb-4">
???? 大規(guī)模搜索過濾器(虛擬化)
</h1>
<input
type="text"
value={query}
onChange={handleSearch}
placeholder="搜索姓名或電子郵件..."
className="w-full p-3 mb-4 border-2 border-pink-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-pink-400"
/>
{isPending && (
<div className="text-pink-500 italic mb-2 text-sm animate-pulse">
?? 過濾中...
</div>
)}
{filteredUsers.length === 0 ? (
<div className="p-4 text-gray-500 italic text-center">未找到用戶。</div>
) : (
<List
height={500}
itemCount={filteredUsers.length}
itemSize={70}
width="100%"
className="border-t border-pink-100"
>
{Row}
</List>
)}
</div>
</div>
);
}
結論
通過應用虛擬化、記憶化和UI 優(yōu)先級策略,我們能夠顯著提高搜索過濾器組件的性能。用戶交互現(xiàn)在變得流暢且響應迅速,即使有數(shù)千個項目需要過濾。
這一經驗凸顯了在處理大數(shù)據(jù)集或復雜用戶交互時優(yōu)化性能的重要性。React 的內置鉤子如 useMemo、useTransition 以及第三方庫如 react-window 可以極大地影響應用的用戶體驗,使其感覺更加流暢和高效。
代碼參考: https://github.com/paghar/react19-next15-trickysample
原文地址: https://dev.to/fpaghar/mastering-usetransition-in-react-building-a-high-performance-search-for-50k-record-case-study-1bdn作者: Fatemeh Paghar