Skip to main content

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;

步驟說明:

  1. 安裝 SWR

    • 在終端機執行以下命令:

      npm install swr
    • 確保已安裝 React 和 ReactDOM。

  2. 定義 fetcher 函數

    • fetcher 負責發送 API 請求並處理回應,拋出錯誤以便 SWR 捕獲。
  3. 管理搜尋狀態

    • 使用 useState 管理搜尋關鍵字(searchTerm)。

    • 根據 searchTerm 動態生成 API URL,若無輸入則設為 null,避免不必要的請求。

  4. 使用 useSWR Hook

    • useSWR 接受動態的 apiUrl 作為查詢鍵,當 searchTerm 改變時,apiUrl 也會改變。

    • SWR 會自動取消舊的請求,僅保留與最新 apiUrl 對應的請求結果,解決競態條件問題。

  5. 處理 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;

步驟說明:

  1. 安裝 React Query

    • 在終端機執行:

      npm install @tanstack/react-query
    • 確保已安裝 React 和 ReactDOM。

  2. 建立 QueryClient

    • 使用 QueryClient 管理查詢和快取,透過 QueryClientProvider 提供給應用程式。
  3. 定義 fetcher 函數

    • fetchPosts 從 queryKey 解構搜尋關鍵字,動態生成 API URL。

    • 加入 _limit=5 限制返回 5 筆資料,模擬真實場景。

  4. 使用 useQuery Hook

    • queryKey 是一個陣列,包含 'posts' 和 searchTerm,確保查詢鍵隨搜尋關鍵字變化。

    • 設定 enabled: !!searchTerm,僅在 searchTerm 非空時發送請求,避免不必要的 API 呼叫。

  5. 處理 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 在競態條件處理上的比較

特性SWRReact 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 提供更進階的控制選項。以上範例展示了如何在搜尋場景中應用這兩個庫,並確保程式碼易於理解和操作。