Effective React Query Keys
Query Keys 是 React Query 的核心概念之一。它們讓函式庫能正確快取資料、在依賴變動時自動 refetch,還能讓你在需要時手動操作 Query Cache,例如 mutation 後更新資料或手動失效某些查詢。
在介紹我自己如何組織 Query Key 之前,先快速說明這三個重點的意義。
資料快取
Query Cache 其實就是一個 JavaScript 物件,key 是序列化後的 Query Key,value 則是查詢資料加上一些 meta 資訊。key 會用確定性 hash 處理,所以你也可以用物件當 key(但最外層還是要是字串或陣列)。
最重要的是 key 必須對每個查詢唯一。React Query 只要在 cache 找到 key,就會直接用。注意:不能用同一個 key 同時給 useQuery
跟 useInfiniteQuery
,因為只有一個 Query Cache,兩者會共用資料,這會出問題,因為 infinite query 跟一般 query 結構完全不同。
useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
})
// 🚨 這樣不行
useInfiniteQuery({
queryKey: ['todos'],
queryFn: fetchInfiniteTodos,
})
// ✅ 請用不同 key
useInfiniteQuery({
queryKey: ['infiniteTodos'],
queryFn: fetchInfiniteTodos,
})
自動 refetch
Queries 是宣告式的。
這個觀念非常重要,值得一再強調,而且一開始很難「開竅」。大多數人會用命令式思維看待查詢與 refetch。
我有一個查詢,抓到資料。現在我按按鈕想 refetch,但參數不同。很多人會這樣寫:
// imperative-refetch
function Component() {
const { data, refetch } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
})
// ❓ refetch 怎麼帶參數?
return <Filters onApply={() => refetch(???)} />
}
答案是:你不用這樣做。
refetch
只會用同一組參數重新抓資料。
如果你有會影響查詢的 state,只要把它放進 Query Key,React Query 會自動在 key 變動時 refetch。所以要套用篩選條件,只要改變 client state:
// query-key-drives-the-query
function Component() {
const [filters, setFilters] = React.useState()
const { data } = useQuery({
queryKey: ['todos', filters],
queryFn: () => fetchTodos(filters),
})
// ✅ 改 local state 就會自動 refetch
return <Filters onApply={setFilters} />
}
setFilters
會觸發 re-render,新的 Query Key 傳給 React Query,就會自動 refetch。更深入例子可參考 practical react query#treat the query key like a dependency array。
手動操作
手動操作 Query Cache 時,Query Key 結構就很重要。像 invalidateQueries 或 setQueriesData 都支援 Query Filters,可以模糊比對 Query Key。
有效設計 Query Key 的建議
以下是我個人經驗(其實整篇都是 XD),不用照抄,但這些策略在專案變複雜時很有用,也很容易擴充。Todo App 當然不用這麼搞 😁。
就近放置(Colocate)
推薦閱讀 Kent C. Dodds 的這篇。我不建議把所有 Query Key 都集中放在 /src/utils/queryKeys.ts
。我會把 Query Key 跟查詢一起放在 feature 目錄下,像這樣:
- src
- features
- Profile
- index.tsx
- queries.ts
- Todos
- index.tsx
- queries.ts
queries
檔案會放所有 React Query 相關內容。我通常只 export 自訂 hook,Query Function 跟 Query Key 都留在本地。