2026/07/13
版本說明 本文範例使用@tanstack/react-queryv5。 TanStack Query v4 與 v5 對載入狀態的命名有所不同。本文使用的isPending、isFetching與isLoading定義皆為 v5
TanStack Query 的 useQuery 預設會在 Component 掛載後自動執行查詢。
但有些資料並不需要一進畫面就取得,例如:
這時可以使用 enabled 控制 Query 何時開始運作。
useQuery({
queryKey: ['example'],
queryFn: fetchExample,
enabled: false,
});當 enabled 為 false 時,Query 不會自動執行。
條件變成 true 後,TanStack Query 才會根據目前的快取狀態,決定是否發送請求。
假設頁面上有多個 Tab,每個 Tab 都有自己的子項目。
需求如下:
如果一進頁面就查詢所有 Tab 的子資料,會產生許多不必要的 API Request。
這種情境很適合使用 enabled。
import { useQuery } from '@tanstack/react-query';
interface UseTabSinglesParams {
laboratoryId: string;
enabled: boolean;
}
export function useTabSingles({
laboratoryId,
enabled,
}: UseTabSinglesParams) {
return useQuery({
queryKey: ['laboratory', laboratoryId, 'singles'],
queryFn: () => getLaboratorySingles(laboratoryId),
enabled: enabled && Boolean(laboratoryId),
staleTime: 5 * 60 * 1000,
});
}這裡的 Query 只有在兩個條件都成立時才會啟用:
enabled && Boolean(laboratoryId)也就是:
laboratoryId 已經存在這可以避免參數尚未準備完成時,送出無效請求。
import { useState } from 'react';
interface TabItemFlyoutProps {
laboratoryId: string;
isActive: boolean;
}
export function TabItemFlyout({
laboratoryId,
isActive,
}: TabItemFlyoutProps) {
const [isFlyoutOpen, setIsFlyoutOpen] = useState(false);
const shouldFetchSingles =
isFlyoutOpen && !isActive;
const {
data: remoteSingles,
isLoading,
isError,
} = useTabSingles({
laboratoryId,
enabled: shouldFetchSingles,
});
const isInitialLoading =
shouldFetchSingles &&
isLoading;
return (
<div
onMouseEnter={() => setIsFlyoutOpen(true)}
onMouseLeave={() => setIsFlyoutOpen(false)}
>
<TabItem />
{isFlyoutOpen && (
<TabSingleFlyout
singles={remoteSingles ?? []}
isLoading={isInitialLoading}
isError={isError}
/>
)}
</div>
);
}最重要的是這段:
const shouldFetchSingles =
isFlyoutOpen && !isActive;它表示:
Flyout 已經開啟,而且目前不是 Active Tab 時,才查詢遠端資料。
Active Tab 的資料通常已經由目前頁面取得,因此不需要再次請求。
不一定。
假設設定如下:
useQuery({
queryKey: ['laboratory', laboratoryId, 'singles'],
queryFn: () => getLaboratorySingles(laboratoryId),
enabled: shouldFetchSingles,
staleTime: 5 * 60 * 1000,
});第一次 Hover:
Cache 沒有資料→ enabled 變成 true→ 發送 API Request→ 結果存入 Cache短時間內再次 Hover:
Cache 已有新鮮資料→ enabled 變成 true→ 直接使用 Cache→ 不重新發送請求超過 staleTime 後再次 Hover:
Cache 資料已過期→ 使用既有 Cache→ 可能進行背景更新因此,enabled 只是控制 Query 是否允許自動執行。
實際是否重新請求,仍然取決於:
staleTimeTanStack Query 預設會把查詢結果視為 stale。
如果 Flyout 子資料不需要每次 Hover 都更新,可以設定:
staleTime: 5 * 60 * 1000代表資料在 5 分鐘內會被視為新鮮資料。
| 操作 | 查詢行為 |
|---|---|
| 第一次 Hover | 發送請求 |
| 5 分鐘內再次 Hover | 使用 Cache,不重新請求 |
| 超過 5 分鐘再次 Hover | 可能背景更新 |
| Hover 不同 Tab | 使用不同 Query Key 查詢 |
Query Key 必須包含 laboratoryId:
queryKey: ['laboratory', laboratoryId, 'singles']這樣每個 Tab 才會有各自獨立的快取。
延遲查詢時,不能只看 isPending。
當 Query 尚未執行,而且 Cache 沒有資料時,即使 enabled 是 false,Query 仍可能是 pending 狀態。
因此第一次載入可以這樣判斷:
const isInitialLoading =
shouldFetchSingles &&
isLoading| 狀態 | 意義 |
|---|---|
isPending | 尚未成功取得過資料 |
isFetching | Query Function 正在執行 |
isLoading (等同 isLoading && isPending, 可參考v5 官方文件) | 正在進行第一次載入 |
這可以避免 Flyout 尚未開啟時就顯示 Loading。
v5 官方文件: 連結
useQuery({
queryKey: ['user', userId],
queryFn: () => getUser(userId),
enabled: Boolean(userId),
});const userQuery = useQuery({
queryKey: ['user', email],
queryFn: () => getUserByEmail(email),
});
const projectsQuery = useQuery({
queryKey: ['projects', userQuery.data?.id],
queryFn: () => getProjects(userQuery.data!.id),
enabled: Boolean(userQuery.data?.id),
});useQuery({
queryKey: ['item-detail', itemId],
queryFn: () => getItemDetail(itemId),
enabled: isModalOpen,
});useQuery({
queryKey: ['current-user'],
queryFn: getCurrentUser,
enabled: authStatus === 'authenticated',
});錯誤:
useQuery({
queryKey: ['singles'],
queryFn: () => getLaboratorySingles(laboratoryId),
});不同 Tab 會共用同一份 Cache,可能顯示錯誤資料。
正確:
useQuery({
queryKey: ['laboratory', laboratoryId, 'singles'],
queryFn: () => getLaboratorySingles(laboratoryId),
});不建議:
useEffect(() => {
if (isFlyoutOpen) {
refetch();
}
}, [isFlyoutOpen, refetch]);可以直接宣告 Query 的啟用條件:
useQuery({
queryKey,
queryFn,
enabled: isFlyoutOpen,
});這樣查詢條件會更清楚,也更符合 TanStack Query 的使用方式。
enabled 適合用來描述:
當某個條件成立後,這筆 Query 才應該開始運作。
在 TabItemFlyout 的案例中:
const shouldFetchSingles =
isFlyoutOpen &&
!isActive &&
Boolean(laboratoryId);可以達成:
laboratoryId 分開快取staleTime 避免重複請求useEffect 手動控制查詢可以把 enabled 理解成 Query 的閘門:
條件不成立→ Query 保持待命 條件成立→ 檢查 Cache 與 staleTime→ 必要時才發送請求