SWR以及react query處理競態條件的範例
什麼是競態條件?
競態條件在前端資料獲取中通常發生在以下場景:
-
使用者快速切換查詢條件(例如搜尋關鍵字或篩選器),導致多個 API 請求幾乎同時發出。
-
後發出的請求可能比先發出的請求更快完成,導致顯示的資料與最終條件不符。
SWR 和 React Query 都有內建機制來處理競態條件,確保最終顯示的資料與最新請求一致。
SWR 處理競態條件的範例
SWR 透過其內建的快取和鍵管理機制,自動處理競態條件。當查詢鍵(key)改變時,SWR 會取消舊的請求並優先使用最新的請求結果。
範例:搜尋使用者名稱
假設我們有一個搜尋框,使用者輸入關鍵字後,SWR 根據輸入內容動態查詢使用者資料。我們將展示如何避免競態條件,確保顯示的結果與最新的搜尋關鍵字一致。
import React, { useState } from 'react';
import useSWR from 'swr';
// 定義 fetcher 函數
const fetcher = (url) => fetch(url).then((res) => {
if (!res.ok) throw new Error('網路請求失敗');
return res.json();
});
function UserSearch() {
// 管理搜尋關鍵字的狀態
const [searchTerm, setSearchTerm] = useState('');
// 動態生成 API URL,根據搜尋關鍵字
const apiUrl = searchTerm
? `https://jsonplaceholder.typicode.com/users?name_like=${searchTerm}`
: null;
// 使用 SWR 獲取資料,當 apiUrl 為 null 時不發送請求
const { data, error, isLoading } = useSWR(apiUrl, fetcher);
// 處理輸入變更
const handleInputChange = (e) => {
setSearchTerm(e.target.value);
};
return (
<div>
<h1>搜尋使用者</h1>
<input
type="text"
value={searchTerm}
onChange={handleInputChange}
placeholder="輸入使用者名稱..."
/>
{isLoading && <div>載入中...</div>}
{error && <div>錯誤:{error.message}</div>}
{data && !isLoading && (
<ul>
{data.length > 0 ? (
data.map((user) => (
<li key={user.id}>{user.name}</li>
))
) : (
<li>無符合結果</li>
)}
</ul>
)}
</div>
);
}
export default UserSearch;
步驟說明:
-
安裝 SWR:
-
在終端機執行以下命令:
npm install swr
-
確保已安裝 React 和 ReactDOM。
-
-
定義 fetcher 函數:
- fetcher 負責發送 API 請求並處理回應,拋出錯誤以便 SWR 捕獲。
-
管理搜尋狀態:
-
使用 useState 管理搜尋關鍵字(searchTerm)。
-
根據 searchTerm 動態生成 API URL,若無輸入則設為 null,避免不必要的請求。
-
-
使用 useSWR Hook:
-
useSWR 接受動態的 apiUrl 作為查詢鍵,當 searchTerm 改變時,apiUrl 也會改變。
-
SWR 會自動取消舊的請求,僅保留與最新 apiUrl 對應的請求結果,解決競態條件問題。
-
-
處理 UI 狀態:
-
根據 isLoading、error 和 data 渲染相應的 UI。
-
若無資料(data 為空陣列),顯示「無符合結果」。
-
競態條件如何解決?
-
SWR 使用查詢鍵(apiUrl)來識別請求。當 searchTerm 改變時,新的 apiUrl 會觸發新的請求,舊的請求會被自動忽略。
-
例如,使用者快速輸入 "John" 然後改為 "Jane",SWR 確保只顯示與 "Jane" 相關的結果。
注意事項:
-
如果 API 不支援模糊搜尋(name_like),可改用其他查詢參數或本地過濾資料。
-
為避免過多請求,可加入防抖(debounce)邏輯,後續可提供相關範例。
React Query 處理競態條件的範例
React Query 透過查詢鍵(queryKey)和內建的請求管理機制,確保最新的請求結果優先顯示,從而避免競態條件。
範例:搜尋貼文標題
假設使用者輸入貼文標題的關鍵字,React Query 根據關鍵字動態查詢貼文列表,並確保結果與最新輸入一致。
import React, { useState } from 'react';
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';
// 建立 QueryClient 實例
const queryClient = new QueryClient();
// 定義 fetcher 函數
const fetchPosts = async ({ queryKey }) => {
const [, searchTerm] = queryKey; // 從 queryKey 解構搜尋關鍵字
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts?title_like=${searchTerm}&_limit=5`
);
if (!response.ok) {
throw new Error('網路請求失敗');
}
return response.json();
};
function PostSearch() {
// 管理搜尋關鍵字的狀態
const [searchTerm, setSearchTerm] = useState('');
// 使用 useQuery 獲取資料
const { data, error, isLoading } = useQuery({
queryKey: ['posts', searchTerm], // 查詢鍵包含搜尋關鍵字
queryFn: fetchPosts,
enabled: !!searchTerm, // 僅在 searchTerm 非空時發送請求
});
// 處理輸入變更
const handleInputChange = (e) => {
setSearchTerm(e.target.value);
};
return (
<div>
<h1>搜尋貼文</h1>
<input
type="text"
value={searchTerm}
onChange={handleInputChange}
placeholder="輸入貼文標題..."
/>
{isLoading && <div>載入中...</div>}
{error && <div>錯誤:{error.message}</div>}
{data && !isLoading && (
<ul>
{data.length > 0 ? (
data.map((post) => (
<li key={post.id}>{post.title}</li>
))
) : (
<li>無符合結果</li>
)}
</ul>
)}
</div>
);
}
// 封裝應用程式
function App() {
return (
<QueryClientProvider client={queryClient}>
<PostSearch />
</QueryClientProvider>
);
}
export default App;
步驟說明:
-
安裝 React Query:
-
在終端機執行:
npm install @tanstack/react-query
-
確保已安裝 React 和 ReactDOM。
-
-
建立 QueryClient:
- 使用 QueryClient 管理查詢和快取,透過 QueryClientProvider 提供給應用程式。
-
定義 fetcher 函數:
-
fetchPosts 從 queryKey 解構搜尋關鍵字,動態生成 API URL。
-
加入 _limit=5 限制返回 5 筆資料,模擬真實場景。
-
-
使用 useQuery Hook:
-
queryKey 是一個陣列,包含 'posts' 和 searchTerm,確保查詢鍵隨搜尋關鍵字變化。
-
設定 enabled: !!searchTerm,僅在 searchTerm 非空時發送請求,避免不必要的 API 呼叫。
-
-
處理 UI 狀態:
-
根據 isLoading、error 和 data 渲染相應的 UI。
-
若無資料,顯示「無符合結果」。
-
競態條件如何解決?
-
React Query 使用 queryKey 來識別請求。當 searchTerm 改變時,新的 queryKey 會觸發新請求,舊請求的結果會被自動忽略。
-
例如,使用者快速輸入 "Post 1" 然後改為 "Post 2",React Query 確保只顯示與 "Post 2" 相關的結果。
注意事項:
-
React Query 的 enabled 選項非常有用,可用於控制請求觸發時機。
-
若需要進一步優化,可設定 staleTime 或使用防抖邏輯:
const { data, error, isLoading } = useQuery({
queryKey: ['posts', searchTerm],
queryFn: fetchPosts,
enabled: !!searchTerm,
staleTime: 30000, // 快取 30 秒
});
SWR 與 React Query 在競態條件處理上的比較
特性 | SWR | React Query |
---|---|---|
競態條件處理 | 自動根據查詢鍵取消舊請求 | 透過查詢鍵和內建機制取消舊請求 |
設定靈活性 | 簡單,無需額外配置 | 支援進階選項(如 enabled、staleTime) |
學習曲線 | 簡單,適合快速上手 | 稍複雜,適合進階場景 |
適用場景 | 簡單的動態查詢 | 複雜的查詢管理和伺服器狀態同步 |
何時選擇哪個?
-
SWR:適合快速開發和簡單的動態查詢場景,特別是與 Next.js 結合時。
-
React Query:適合需要進階控制(例如條件觸發、快取時間)或複雜資料操作的應用。
進階優化:加入防抖(Debounce)
若使用者快速輸入可能導致過多 API 請求,可使用防抖來減少請求頻率。以下是簡單的防抖範例(適用於 SWR 和 React Query):
import { useState, useEffect } from 'react';
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
}
// 在元件中使用
const debouncedSearchTerm = useDebounce(searchTerm, 500); // 延遲 500ms
// 將 debouncedSearchTerm 替換原始的 searchTerm 用於 API 請求
結論
SWR 和 React Query 都能有效處理競態條件,透過查詢鍵的動態更新,確保最新請求的結果優先顯示。SWR 適合簡單場景,React Query 提供更進階的控制選項。以上範例展示了如何在搜尋場景中應用這兩個庫,並確保程式碼易於理解和操作。