測驗:TanStack Query 基礎:useQuery 與自動快取
共 5 題,點選答案後會立即顯示結果
1. 使用 TanStack Query 時,QueryClientProvider 應該放在應用程式的哪個位置?
2. useQuery 的兩個必要參數是什麼?
3. 關於 isLoading 和 isFetching 的差異,下列敘述何者正確?
4. staleTime 的預設值是多少?這代表什麼意義?
5. 關於 Query Key 的設計,下列哪種做法是文章建議的?
// 選項 A
queryKey: ‘users’
// 選項 B
queryKey: [‘users’, userId]
// 選項 C
queryKey: { type: ‘users’, id: userId }
// 選項 D
queryKey: `users-${userId}`
前言
上一篇我們認識了 Server State 與 Client State 的差異,了解為什麼需要專門的工具來處理「來自伺服器的資料」。這篇文章將帶你實際動手,學會使用 TanStack Query 的核心功能:useQuery。
當你在專案中看到這樣的程式碼時:
const { data, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
});
Code language: JavaScript (javascript)讀完這篇後,你將能夠完全理解這段程式碼的運作原理,以及背後的快取機制如何讓你的應用程式更有效率。
學習目標
讀完本篇後,你將能夠:
- 安裝並設定 TanStack Query 環境
- 使用 useQuery 取得遠端資料並處理 loading/error 狀態
- 理解 Query Key 的設計原則與快取機制
安裝與基礎設定
安裝套件
npm install @tanstack/react-query
# 或
yarn add @tanstack/react-query
Code language: CSS (css)設定 QueryClientProvider
TanStack Query 需要一個「查詢客戶端」來管理所有的快取和查詢狀態。這個設定通常放在應用程式的最外層:
// main.jsx 或 App.jsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
// 建立 QueryClient 實例
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<YourApp />
</QueryClientProvider>
);
}
Code language: JavaScript (javascript)讀程式碼時的觀察重點:
QueryClient是快取的核心,整個應用程式共用一個實例QueryClientProvider透過 React Context 把 queryClient 傳遞給所有子元件- 任何使用
useQuery的元件都必須在QueryClientProvider內部
useQuery 基本用法
最簡單的範例
import { useQuery } from '@tanstack/react-query';
function UserList() {
const { data, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: async () => {
const response = await fetch('/api/users');
return response.json();
},
});
if (isLoading) return <div>載入中...</div>;
if (error) return <div>發生錯誤:{error.message}</div>;
return (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
Code language: JavaScript (javascript)useQuery 的兩個必要參數
| 參數 | 說明 |
|---|---|
queryKey |
查詢的唯一識別碼,用於快取管理 |
queryFn |
實際取得資料的非同步函式 |
讀程式碼時的理解方式:
queryKey: ['users']:這是這筆資料的「身分證」,TanStack Query 用它來決定要從快取拿資料還是重新請求queryFn:當需要取得新資料時,會呼叫這個函式
回傳值解構
useQuery 回傳一個物件,常用的屬性包括:
const {
data, // 查詢成功後的資料
isLoading, // 是否正在載入(第一次載入)
isFetching, // 是否正在取得資料(包含背景更新)
error, // 錯誤物件(如果有的話)
isError, // 是否發生錯誤
isSuccess, // 是否成功取得資料
refetch, // 手動重新取得資料的函式
} = useQuery({ ... });
Code language: JavaScript (javascript)isLoading vs isFetching 的差異:
isLoading:只在「第一次載入」且「沒有快取資料」時為 trueisFetching:只要正在發送請求就為 true(包含背景更新)
// 實務上常見的判斷方式
if (isLoading) {
// 顯示骨架畫面或 spinner(第一次載入)
}
// 如果你想在背景更新時也顯示提示
if (isFetching && !isLoading) {
// 顯示「更新中」的小提示
}
Code language: JavaScript (javascript)快取機制:staleTime 與 gcTime
TanStack Query 最強大的功能之一就是自動快取。理解這兩個時間設定,是掌握快取行為的關鍵。
staleTime(資料過期時間)
const { data } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
staleTime: 5 * 60 * 1000, // 5 分鐘
});
Code language: JavaScript (javascript)staleTime 的意義:
- 資料在這段時間內被視為「新鮮的」(fresh)
- 新鮮的資料不會觸發背景更新
- 預設值是
0,表示資料一取得就立刻過期
實務場景:
// 使用者資料很少變動,設定 5 分鐘
useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
staleTime: 5 * 60 * 1000,
});
// 即時股價需要頻繁更新,設定 10 秒
useQuery({
queryKey: ['stock', symbol],
queryFn: () => fetchStockPrice(symbol),
staleTime: 10 * 1000,
});
Code language: JavaScript (javascript)gcTime(垃圾回收時間)
const { data } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
gcTime: 10 * 60 * 1000, // 10 分鐘
});
Code language: JavaScript (javascript)gcTime 的意義:
- 當沒有任何元件使用這筆快取時,經過 gcTime 後會被清除
- 預設值是
5 60 1000(5 分鐘) - 在 v4 之前稱為
cacheTime
快取生命週期圖解
使用者進入頁面
↓
[查詢開始] ──→ [資料取得] ──→ [資料新鮮期間]
(staleTime 內)
↓
[資料過期]
(staleTime 後)
↓
┌─────────────────────────────────────┐
│ 觸發背景更新的條件: │
│ • 元件重新掛載 │
│ • 視窗重新獲得焦點 │
│ • 網路恢復連線 │
│ • 手動呼叫 refetch │
└─────────────────────────────────────┘
↓
[元件卸載/不再使用]
↓
[gcTime 倒數開始]
↓
[快取被清除]
Code language: CSS (css)Query Key 設計模式
Query Key 是 TanStack Query 最重要的概念之一。它不只是識別碼,更決定了快取如何被管理和失效。
基本原則:使用陣列結構
// 好的設計:使用陣列,有層級關係
queryKey: ['users']
queryKey: ['users', userId]
queryKey: ['users', userId, 'posts']
queryKey: ['users', { status: 'active' }]
// 避免:單一字串(雖然技術上可行)
queryKey: 'users' // 不建議
Code language: JavaScript (javascript)依賴參數的處理
當查詢依賴某些參數時,把參數放入 queryKey:
function UserProfile({ userId }) {
const { data } = useQuery({
queryKey: ['users', userId], // userId 是依賴參數
queryFn: () => fetchUser(userId),
});
}
Code language: JavaScript (javascript)為什麼要這樣設計?
- 不同的 userId 會有不同的快取
- 當 userId 改變時,自動使用新的快取或發送新請求
- 可以用
['users']一次讓所有使用者相關的快取失效
包含篩選條件
function UserList({ filters }) {
const { data } = useQuery({
queryKey: ['users', filters], // filters 物件也放入 key
queryFn: () => fetchUsers(filters),
});
}
// 使用範例
<UserList filters={{ status: 'active', role: 'admin' }} />
Code language: JavaScript (javascript)讀程式碼時的觀察重點:
- queryKey 包含了所有會影響查詢結果的參數
- 物件會被深度比較,內容相同就會使用同一份快取
Query Key 的階層關係
// 這些 key 有階層關係
['todos'] // 所有待辦事項
['todos', 'list'] // 待辦列表
['todos', 'list', { status: 'done' }] // 已完成的待辦列表
['todos', 'detail', 1] // 單一待辦詳情
// 可以一次讓整個階層失效
queryClient.invalidateQueries({ queryKey: ['todos'] });
// 上面這行會讓所有以 ['todos'] 開頭的查詢都失效
Code language: JavaScript (javascript)DevTools 安裝與使用
TanStack Query 提供了強大的開發者工具,幫助你觀察快取狀態和除錯。
安裝
npm install @tanstack/react-query-devtools
Code language: CSS (css)加入應用程式
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<YourApp />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
Code language: JavaScript (javascript)DevTools 功能
開啟 DevTools 後,你可以:
- 查看所有快取的查詢:每個 queryKey 對應的資料和狀態
- 觀察狀態變化:fresh、stale、fetching、inactive 等狀態
- 手動觸發動作:refetch、invalidate、reset、remove
- 檢視查詢細節:最後更新時間、資料內容、錯誤訊息
除錯技巧:
- 如果資料沒有更新,檢查 staleTime 設定
- 如果快取消失太快,檢查 gcTime 設定
- 如果看到重複請求,檢查 queryKey 是否正確
完整範例
讓我們把學到的概念整合成一個完整範例:
// api.js - 定義 API 函式
export const fetchUsers = async (filters) => {
const params = new URLSearchParams(filters);
const response = await fetch(`/api/users?${params}`);
if (!response.ok) {
throw new Error('取得使用者列表失敗');
}
return response.json();
};
export const fetchUser = async (userId) => {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error('取得使用者資料失敗');
}
return response.json();
};
Code language: JavaScript (javascript)// hooks/useUsers.js - 封裝 Query Hook
import { useQuery } from '@tanstack/react-query';
import { fetchUsers, fetchUser } from '../api';
export function useUsers(filters) {
return useQuery({
queryKey: ['users', filters],
queryFn: () => fetchUsers(filters),
staleTime: 60 * 1000, // 1 分鐘
});
}
export function useUser(userId) {
return useQuery({
queryKey: ['users', userId],
queryFn: () => fetchUser(userId),
staleTime: 5 * 60 * 1000, // 5 分鐘
enabled: !!userId, // 只有當 userId 存在時才執行查詢
});
}
Code language: JavaScript (javascript)// components/UserList.jsx
import { useUsers } from '../hooks/useUsers';
function UserList({ filters }) {
const { data, isLoading, error, isFetching } = useUsers(filters);
if (isLoading) {
return <div className="skeleton">載入中...</div>;
}
if (error) {
return <div className="error">錯誤:{error.message}</div>;
}
return (
<div>
{isFetching && <span className="updating">更新中...</span>}
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
Code language: JavaScript (javascript)讀程式碼時的重點整理:
- API 函式與 Query Hook 分離,職責清晰
enabled選項可以控制查詢是否執行isLoading處理初次載入,isFetching處理背景更新- 錯誤處理透過 throw Error,TanStack Query 會自動捕捉
本篇小結
這篇文章涵蓋了 TanStack Query 的核心基礎:
| 概念 | 重點 |
|---|---|
| QueryClientProvider | 應用程式最外層設定,提供快取管理 |
| useQuery | 取得資料的主要 Hook,回傳 data、isLoading、error 等 |
| queryKey | 快取的唯一識別碼,使用陣列結構 |
| queryFn | 實際取得資料的非同步函式 |
| staleTime | 資料保持新鮮的時間,過期後觸發背景更新 |
| gcTime | 快取被清除前的保留時間 |
| DevTools | 開發階段觀察快取狀態的好幫手 |
下一篇預告
下一篇我們將學習 useMutation,處理新增、更新、刪除等「會改變伺服器資料」的操作,以及如何在操作後讓相關快取失效,保持資料一致性。
進階測驗:TanStack Query 基礎:useQuery 與自動快取
測驗目標:驗證你是否能在實際情境中應用所學。
共 5 題,包含情境題與錯誤診斷題。
共 5 題,包含情境題與錯誤診斷題。
1. 你正在開發一個股票報價頁面,需要顯示即時股價。考慮到股價變動頻繁,你應該如何設定 staleTime? 情境題
useQuery({
queryKey: [‘stock’, symbol],
queryFn: () => fetchStockPrice(symbol),
staleTime: ???,
});
2. 你需要在使用者列表頁面中實作篩選功能。當篩選條件改變時,你希望自動使用新的快取或發送新請求。應該如何設計 Query Key? 情境題
function UserList({ filters }) {
const { data } = useQuery({
queryKey: ???,
queryFn: () => fetchUsers(filters),
});
}
3. 你在開發使用者詳情頁面,但發現當 userId 還沒有值時(例如來自 URL 參數但還沒解析完),會發送一個無效的 API 請求。如何避免這個問題? 情境題
function UserProfile({ userId }) {
// userId 可能一開始是 undefined
const { data } = useQuery({
queryKey: [‘users’, userId],
queryFn: () => fetchUser(userId),
// 需要加入什麼選項?
});
}
4. 小明在使用 TanStack Query 時遇到錯誤。他的程式碼如下,但 useQuery 無法正常運作:錯誤診斷
// App.jsx
import { useQuery } from ‘@tanstack/react-query’;
function App() {
const { data, isLoading } = useQuery({
queryKey: [‘users’],
queryFn: fetchUsers,
});
if (isLoading) return <div>載入中…</div>;
return <div>{data.length} 位使用者</div>;
}
export default App;
5. 小華發現他的應用程式在切換頁面後,快取資料很快就消失了,每次返回同一頁都要重新載入。他的設定如下,問題最可能出在哪裡?錯誤診斷
const { data } = useQuery({
queryKey: [‘products’],
queryFn: fetchProducts,
staleTime: 5 * 60 * 1000, // 5 分鐘
gcTime: 10 * 1000, // 10 秒
});