如何在 React Query 中善用 Placeholder 與 Initial Data
今天這篇文章要討論如何在使用 React Query 時提升使用者體驗。大多數時候,我們(還有使用者)都不喜歡看到惱人的 loading spinner。雖然有時候 spinner 是必要的,但如果能避免,當然最好。
React Query 已經提供了很多工具,讓我們在許多情境下可以不用顯示 loading spinner。例如:
- 從快取拿到 stale data,同時在背景自動更新
- 預先抓資料(prefetch),如果你知道之後會用到
- 查詢 key 變動時保留前一筆資料,避免硬 loading 狀態
另一種方式是同步預先填好快取,給一份你認為「很可能正確」的資料。React Query 提供兩種做法:Placeholder Data 和 Initial Data。
先來看看它們的共通點,再說明差異,以及什麼情境該用哪一種。
共同點
如前所述,這兩種方式都能讓你用同步取得的資料預先填快取。只要有設定其中一個,查詢就不會進入 loading 狀態,而是直接 success。而且兩者都可以直接給值,也可以給一個回傳值的函式(如果計算很耗效能):
// success-queries
function Component() {
// ✅ 即使還沒抓到資料,status 也會是 success
const { data, status } = useQuery({
queryKey: ['number'],
queryFn: fetchNumber,
placeholderData: 23,
})
// ✅ initialData 也一樣
const { data, status } = useQuery({
queryKey: ['number'],
queryFn: fetchNumber,
initialData: () => 42,
})
}
最後,如果快取裡已經有資料,這兩個選項都不會有作用。
那到底有什麼差別?要理解這點,我們要先簡單認識 React Query 的「層級」運作方式:
Cache 層級
每個 Query Key 只會有一個 cache entry。這很直觀,因為 React Query 的強大之處就在於能「全域」共用同一份資料。
有些 useQuery 的選項會影響 cache entry,例如 queryFn、gcTime。因為只有一個 cache entry,這些選項就是決定怎麼取得資料、什麼時候可以被回收。
Observer 層級
Observer(觀察者)大致上就是每個 cache entry 的一個訂閱。Observer 會監看 cache entry 的變化,只要有變就會被通知。
最基本的 observer 就是呼叫 useQuery。每呼叫一次就建立一個 observer,元件資料有變就 re-render。當然,也可以有多個 observer 監看同一個 cache entry。
你可以在 React Query Devtools 裡看到每個查詢有幾個 observer(左邊的數字):
有些選項是 observer 層級,例如 select 或 refetchInterval。select 特別適合資料轉換 (react-query-data-transformations#3-using-the-select-option),因為可以讓不同元件訂閱同一份快取,但各自取不同資料片段。