[npm] react-query
此篇為各筆記之整理,非原創內容,資料來源可見下方連結與文後參考資料:
- React Query: Server State Management in React @ Udemy
- Mastering Mutations in React Query @ TkDodo's blog
- TanStack Query
練習用 repo @ gitlab
部分 API 或用法在 React Query v4 可能已不適用,詳情參考:Migrating to React Query 4。
Concept
React Query 的作用(How)
React-query 會介於 React 和 API Server(Data) 之間,所有透過 React 向 Server 發送的請求都會先經過 react-query。
react-query 在 React 和 server 之間扮演了資料管理和 cache 的角色,開發者需要告知 react-query 什麼時候要更新 cache、什麼時候要重新向 server 發送 API 請求。
解決了什麼(Why)
Client State vs. Server State
- client state:和瀏覽器本身較相關的狀態,例如,使用者選擇的 theme、language 等
- server state:資料本身保存在伺服器,但需要用來顯示在 UI 上的
status vs. fetchStatus
Why two different states @ TanStack Query v4
使用 React Query v4 拉取資料是,會有兩個狀態:
status
:指的是 data 的狀態,用來判斷「資料是否存在」,其狀態型別為QueryStatus
,包含:loading
、error
、success
fetchStatus
:指的是queryFn
的狀態,用來判斷「queryFn 是否有在執行」,其狀態型別為FetchStatus
,包含:fetching
、pause
、idle
。
staleTime vs. cacheTime
staleTime — 和重新拉取資料的時間有關
staleTime
指的是資料多久後算過期(stale),決定了什麼時候要向 server 發送請求以取得最新的資料,過期後的 query 才會需要要向 server 發送請求以取得最新的資料,預設是 0。
react-query 並不會在該 query stale 後就立即向 server 發送請求去取得最新的資料,還需要特定的 trigger 下才會執行 refetch 的動作,這些 trigger 像是 window 再次被 focus 時、元件重新 mount 等等,可以參考 useQuery API 中對於 refetch 的說明。
cacheTime:inactive query 的資料多久後要清除 — 和記憶體管理有關
如果 react-query 中有 cache 的資料,則 useQuery 會先回傳這個 cache 來呈現在 UI 上,但要留意實際使用時如果畫面上先看到舊資料(cache)才瞬間更新成新資料時適不適當。這些 cache 被清除的時間則取決於 cacheTime
的設定。
當原本的 queryKey
並沒有在當前 UI 上被 useQuery()
使用到時,這個 query 會馬上進到 inactive
的狀態。但因為 inactive query 中的資料仍有可能再不久後被使用到,因此並不會馬上就把這個 query 及其取得的資料給清除。
開發者可以透過 cacheTime
的設定來決定 inactive query 要在多久後被清除(garbage collected),一旦超過所設定的 cache time,這個 query 及其 data 都會被清除(garbage collected),預設是 5 mins。
cacheTime 的設定和資料是否要在向 server 請求一次無關,只和 cache 的資料要保存多久有關。
cacheTime 理應要比 staleTime 來的更久,如果 cacheTime 小於 staleTime 最好調整一下設定。
在 react-query 的 devtool 中可以看到那些 query 是屬於 inactive 的,這些 inactive query 所取得的資料會在過了設定的 cacheTime
後被清除:
query status
useQuery
的 status 一共包含:
- idle
- error
- loading
- success
isFetching vs. isLoading
isFetching
是送出 API 請求但該請求還沒被 resolvedisLoading
指的是除了正在 fetching 之外,同時沒有 cached data,也就是 "isFetching + no cached data"。它是isFetching
的子集合(subset),所以 isLoading 時,一定也會是 isFetching。
cache level and observer level
Placeholder and Initial Data in React Query @ TkDodo's blog
在 react-query 中,不同的設定會作用在不同的層級上,可以簡單分成 cache level 和 observer level:
Global:以 cache level 來說,每一個 query key 都會對應到一組 cache entry,這是「一對一的關係」,透過 query key 就可以讓我們在整個 App 中讀取到這些 query data。useQuery 中的一些設定就是直接作用在 cache level 的,例如 stateTime
和 cacheTime
,這些設定就是會跟著這個 cache entry(全域都一樣)。
Local:另一個是 observer level,每當使用一次 useQuery
就會建立一個 observer,每一個 observer 同樣會根據 query key 對應到一組 cache entry,但這是「多對一的關係」,也就是說,可以有多個 observer 都在監控同一組 cache entry(即,使用 useQuery
且帶入相同的 query key),每當資料有改變的時候,就會觸發元件重新轉譯(re-render)。useQuery 中的一些設定則是作用在 observer level,例如,select
或 keepPrevious
,這些設定會隨著不同的 useQuery 而有不同。
Getting Started
建立 QueryClient
+import { QueryClient, QueryClientProvider } from "react-query";
+const queryClient = new QueryClient();
function App() {
return (
+ <QueryClientProvider client={queryClient}>
<div className="App">
<h1>React Query</h1>
</div>
+ </QueryClientProvider>
);
}
整合 devtool
import { QueryClient, QueryClientProvider } from "react-query";
+import { ReactQueryDevtools } from "react-query/devtools";
const queryClient = new QueryClient();
</div>
+ <ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
Query Keys
- Query Keys @ react-query
- Effective React Query Keys @ Effective React Query Keys
string-only query keys
如果只使用 string 當作 query key 的話,實際上 react query 會把它轉成只有一個元素的 key,也就是說:
useQuery('todos' /*...*/); // queryKey === ['todos']
所有的 keys 都會被 hashed
react query 的 key 如果物件的話,因為會經過 hashed,所以物件中只要屬性都一樣,不管順序對 react-query 來說就是相同的 key;但如果是陣列的話,不同的順序就會是不同的 key。
Effective React Query Keys
在 TkDodo 的部落格中列出了幾點他個人喜歡的做法。
Colocate
不需要把所有的 query key 做集中式的管理,像是 src/utils/queryKeys.ts
,而是直接將這些 query key 管理在會用到的 query 旁,並擺放在以功能區分的資料夾中,像是:
https://tkdodo.eu/blog/effective-react-query-keys#colocate
- src
- features
- Profile
- index.tsx
- queries.ts
- Todos
- index.tsx
- queries.ts
總是使用 Array Keys
雖然 query keys 可以是 string,但實際上它依然會被轉成陣列,未來讓事情更單純,總是用 Array keys 吧。