用React Query 來管理數據請求
背景
在項目中,通常都需要跟服務端進行異步的數據交互,這包括查询
和变更
。
以一個簡單的列表查詢為例,我們通過axios
去請求服務端的列表數據:

OK ! 數據已經成功的取到了,也就是我們完成了跟服務端的一次查詢交互了。現在我們來嘗試更進一步,在React
中可以通過實現一個Hooks
把查詢做的更優雅一點:

Perfect !? 並沒有!
我們遺漏了非常重要的請求狀態的處理,包括異常和請求進行中的情況,讓我們繼續完善useListQuery
:

以上,就是一個典型的請求處理的場景,為了實現它,我們寫了近30 行代碼... <del>用來刷代碼行數也是極其不錯的... </del>
那難道沒有一種標準的請求處理模式嗎?當然有!接下來我們進入正題,來看看ReactQuery
的解決方案。
請求處理模式
初識ReactQuery的第一印象,通常都源於它提供的開箱即用的Query
和Mutation
的API.
哦,React 有一個請求庫了
這就是ReactQuery能力的第一重境界--請求處理。
它通過useQuery
、 useMutation
等Hooks API,提供了一系列標準的請求處理模式。
那麼首先來看看ReactQuery 是怎麼處理我們的列表請求的:

useQuery
通常包含兩個參數:
- 一個能唯一標識這個請求的
Query key
- 一個真正執行請求並返回數據的異步方法
ReactQuery 的緩存策略是基於這個key 來實現的。 key 值除了字符串外,還可以是一個數組或者對象:
useQuery('list',...)useQuery(['list'],...)// 数组或对象作为 key 时通常都包含查询条件
useQuery(['list',1],...)userQuery(['list',{page:1}])useQuery({type:'list',page:1})
Query key唯一的要求就是可以被序列化。
而對於請求方法, useQuery
要求是一個then-able
的函數即可,在我們日常使用情況中,通常指代的就是返回Promise,而Promise的返回值即請求的響應數據。
更多關於
userQuery
的用法可直接參考userQuery API Reference
與useQuery
類似,ReactQuery也提供了數據變更的Hooks API:

useMutation
的參數通常包含一個真正執行請求的異步方法,返回值第一項為邏輯完備的mutate
異步方法,在按鈕點擊後,可以通過調用mutate
提交數據以及狀態的處理。
更多關於
useMutation
的用法可直接參考useMutation API Reference
ReactQuery 在請求處理上給我們提供了一個標準的處理方案,但是它的角色遠不止請求庫這麼簡單。在官方文檔的Overview 中作者就給了一個定位:
React Query is often described as the missing data-fetching library for React, but in more technical terms, it makes fetching, caching, synchronizing and updating server state in your React applications a breeze.
那麼接下來,進入ReactQuery的第二重境界--全局服務端狀態管理。
Global Server State Management
什麼是Server State
首先,我們需要知道什麼是服務端狀態。在無意識的行為中,我們通常都將所有的組件渲染所需要的數據都放在一起管理,比如放在State 中或者通過Redux 這類狀態管理庫來管理。
然而,我們再來斟酌一下我們的數據,是不是通常都有明顯的來源特徵:
- 列表數據、詳情數據等通過調接口由服務端提供的數據;
- 選中狀態、折疊狀態這類由客戶端來維護的狀態;
基於數據的來源,我們就可以將組件渲染所需要的狀態分為服務端狀態和客戶端狀態。
ReactQuery的狀態管理
ReactQuery就將我們所有的服務端狀態維護在全局,並配合它的緩存策略來執行數據的存儲和更新。借助於這樣的特性,我們就可以將所有跟服務端進行交互的數據從類似於Redux
這樣的狀態管理工具中剝離,而全部交給ReactQuery來管理。
ReactQuery 會在全局維護一個服務端狀態樹,根據Query key 去查找狀態樹中是否有可用的數據,如果有則直接返回,否則則會發起請求,並將請求結果以Query key 為主鍵存儲到狀態樹中。
緩存
ReactQuery的緩存策略使用了stale-while-revalidate
.在MDN的Cache Control中對這個緩存策略的解釋是:
客戶端願意接受陳舊的響應,同時在後台異步檢查新的響應
在ReactQuery中的體現是,可以接受狀態樹中存儲的stale
狀態數據,並且會在緩存失效、新的查詢實例被構建或refetch
等行為後執行更新狀態。
關於ReactQuery緩存的處理過程,官方給了一個詳細的範例
ReactQuery 還能解決這些問題
刷新列表狀態
日常開發工作中經常需要處理在添加、刪除或者編輯後刷新列表的數據。為了實現這個行為,我們通常需要將列表數據的狀態抽取到列表和詳情的父組件中去管理:

然而,在模式上,列表數據的只是List
組件的數據源,應該收斂到List
組件中去管理,而不應該放在父組件中。那如果這樣的話,兄弟組件之間如何通信以達到更新列表數據的目的?似乎問題變得越來越複雜...
不怕! ReactQuery來幫我們解這個問題~前面我們說過ReactQuery維護的是一個全局的狀態樹,那既然是全局的,問題不就簡單了:
餵!詳情頁數據已經提交了,幫我更新下Query Key為
list
的數據吧!

ReactQuery提供了queryCache.invalidateQueries
可以直接指定某個Query key的緩存數據失效,這樣ReactQuery就會在後台自動重新拉取最新的數據並更新到狀態樹中,這樣列表組件中就渲染最新的數據了!完美!
usePaginatedQuery和useInfiniteQuery
除了基礎的useQuery
外,ReactQuery還提供了usePaginatedQuery
和useInfiniteQuery
,分別來處理分頁和無限加載兩個細分場景下的查詢。
通過上述API,可以讓我們在代碼中免去維護類似於分頁這樣的客戶端狀態。
const[pagination,setPagination]=React.useState({currentPage:1,pageSize:10,totalSize:0});
⚠️在使用
usePaginatedQuery
和useInfiniteQuery
時,一定要保證Query key的唯一性,否則會造成難以預料的分頁數據的混亂。
更多
除了本文中提到的這些基礎能力外, ReactQuery
還提供了Prefetching
、 SSR
、 Optimistic Updates
等高級特性。
另外,除了我們上面用到的queryCache.invalidateQueries
, queryCache
還包含很多API來方便我們做一些手動的的狀態操作。
重要提示
如果您看了本篇文章並開始使用ReactQuery了,請一定不要忽視官方文檔中的Guides & Concepts: Important Defaults中的Important Defaults
,了解ReactQuery有那些默認的行為設置,這會幫助您構建更健壯的應用。