使用React Query做為axios請(qǐng)求庫的上層封裝
前言
在項(xiàng)目中,通常都需要跟服務(wù)端進(jìn)行異步的數(shù)據(jù)交互,基本都是用到axios這個(gè)庫來做請(qǐng)求,嗯,畢竟擁有80k star,明星項(xiàng)目
接下來,我們來回顧下axios在項(xiàng)目中的使用
以查詢用戶信息為例,我們會(huì)這樣封裝
async function requestUsers(){
const {data} =await axios.get('/api/users');
return data;
}
我們?cè)儆胔ooks再封裝下這個(gè)請(qǐng)求,包括loading等中間態(tài)的封裝,處理的優(yōu)雅一點(diǎn)
import React, {useState,useEffect} from 'react';
import axios from 'axios';
function useUsersQuery(){
const [data,setData] = useState([]);
const [isLoading,setLoading] = useState(false);
const [isError,setError] = useState(false)
useEffect(()=>{
(async()=>{
setLoading(true);
try{
const {data} = await axios.get('/api/users');
setData(data);
} catch((()=>{
setError(true);
})
setLoading(false);
})()
})
return {
data,
isLoading,
isError
};
}
function UserList(){
const {data, isLoading,isError} = useUsersQuery();
if (isLoading) {
return <div>loading</div>;
}
if (isError) {
return <div>error</div>;
}
return (
<div>
{
data.map((item)=>{
return <div>{item.name}</div>
})
}
</div>
)
}
可以看到,我們的項(xiàng)目中基本上是這樣封裝請(qǐng)求,我們不僅要請(qǐng)求數(shù)據(jù),還要處理相應(yīng)的loading,error這些中間態(tài),這類通用的中間狀態(tài)處理邏輯可能在不同組件中重復(fù)寫很多次。
另外,現(xiàn)在的前端項(xiàng)目特別是單頁面應(yīng)用,會(huì)使用Flux、Redux、Mobox等狀態(tài)管理庫,會(huì)把組件間共享的數(shù)據(jù)都存放在狀態(tài)管理庫中,這些可以分為兩類,一類是用戶交互的中間狀態(tài),比如isLoading,isClose,modalVisible等等,另外一類就是服務(wù)端狀態(tài)(數(shù)據(jù))
我們一般處理的方式都是無差別的存放在全局狀態(tài)管理上,狀態(tài)管理庫為了兼容異步請(qǐng)求,就有了redux-saga,redux-action這些異步解決方案
其實(shí)對(duì)于redux等狀態(tài)管理庫,本身是沒有異步這個(gè)概念,只有mutation這種操作,為了支持異步,硬是強(qiáng)加了異步action這種操作,實(shí)際這些異步中間件就是在最后的請(qǐng)求回調(diào)透?jìng)髁薲ispatch,諸如這些情況,我們不僅將數(shù)據(jù)一鍋燉放在全局狀態(tài)管理上,寫法上也使得項(xiàng)目越來越臃腫了(以至于出現(xiàn)后面rematch、dva方案進(jìn)行簡(jiǎn)化),我們有沒有想過,服務(wù)端的狀態(tài)就不應(yīng)該放在全局狀態(tài)管理上,全局狀態(tài)管理應(yīng)該專門處理用戶交互的中間狀態(tài)

接下來,就是引出今天的主角 React Query
React Query
React Query 通常被描述為 React 缺少的數(shù)據(jù)獲取(data-fetching)庫,但是從更廣泛的角度來看,它使 React 程序中的獲取,緩存,同步和更新服務(wù)器狀態(tài)變得輕而易舉。
解決了什么問題
服務(wù)端狀態(tài)有以下特點(diǎn):
存儲(chǔ)在遠(yuǎn)端,本地?zé)o法直接控制
需要異步 API 來查詢和更新
可能在不知情的情況下,被另一個(gè)請(qǐng)求方更改了數(shù)據(jù),導(dǎo)致數(shù)據(jù)不同步
現(xiàn)有的狀態(tài)管理庫(如 Mobx、Redux等)適用于管理客戶端狀態(tài),但它們并不關(guān)心客戶端是如何異步請(qǐng)求遠(yuǎn)端數(shù)據(jù)的,所以他們并不適合處理異步的、來自服務(wù)端的狀態(tài)。
而 React Query 就是為了解決服務(wù)端狀態(tài)帶來的上述問題而出現(xiàn)的,除此之外它還帶來了以下特性:
更方便地控制緩存
把對(duì)于相同數(shù)據(jù)的多個(gè)請(qǐng)求簡(jiǎn)化成一個(gè)
在后臺(tái)更新過期數(shù)據(jù)
知道數(shù)據(jù)什么時(shí)候會(huì)「過期」
對(duì)于數(shù)據(jù)的變化盡可能快得做出響應(yīng)
分頁查詢和懶加載等請(qǐng)求性能優(yōu)化
管理服務(wù)器狀態(tài)的內(nèi)存和垃圾回收
通過結(jié)構(gòu)共享(structural sharing)來緩存查詢結(jié)果
請(qǐng)求中間態(tài)處理
function Todos() {
const { isLoading, isError, data, error } = useQuery('todos', fetchTodoList)
if (isLoading) {
return <span>Loading...</span>
}
if (isError) {
return <span>Error: {error.message}</span>
}
// also status === 'success', but "else" logic works, too
return (
<ul>
{data.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
)
}
React query會(huì)自動(dòng)把這些isLoading,isError請(qǐng)求中間態(tài)處理好,我們不必寫重復(fù)邏輯,另外配合Suspense提對(duì)一點(diǎn)對(duì)于loading場(chǎng)景的處理,Suspense也支持的不錯(cuò),特別是局部Loading,簡(jiǎn)直Nice!
ReactQuery 的狀態(tài)管理
Fetch, cache and update data in your React and React Native applications all without touching any "global state".
官網(wǎng)對(duì)于React Query的簡(jiǎn)述,注意global state,你會(huì)不解,為什么React Query明明是一個(gè)請(qǐng)求庫,跟數(shù)據(jù)狀態(tài)管理又有什么關(guān)系,甚至可以處做全局狀態(tài)管理
那是因?yàn)镽eactQuery 會(huì)在全局維護(hù)一個(gè)服務(wù)端狀態(tài)樹,根據(jù) Query key 去查找狀態(tài)樹中是否有可用的數(shù)據(jù),如果有則直接返回,否則則會(huì)發(fā)起請(qǐng)求,并將請(qǐng)求結(jié)果以 Query key 為主鍵存儲(chǔ)到狀態(tài)樹中。
ReactQuery 就將我們所有的服務(wù)端狀態(tài)維護(hù)在全局,并配合它的緩存策略來執(zhí)行數(shù)據(jù)的存儲(chǔ)和更新。借助于這樣的特性,我們就可以將所有跟服務(wù)端進(jìn)行交互的數(shù)據(jù)從類似于 Redux 這樣的狀態(tài)管理工具中剝離,而全部交給 ReactQuery 來管理。
舉個(gè)例子:
import React from "react";
import { useQuery, queryCache } from "react-query";
import "./styles.css";
export default function App() {
return (
<div className="App">
<h1>Shared state using react-query</h1>
<Comp1 />
<Comp2 />
</div>
);
}
function useSharedState(key, initialValue) {
const { data: state } = useQuery(key, () => queryCache.getQueryData(key), {
initialData: initialValue
});
const setState = value => queryCache.setQueryData(key, value);
return [state, setState];
}
function Comp1() {
const [count, setCount] = useSharedState("count", 1);
console.log("comp1 rendered");
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
</div>
);
}
function Comp2() {
const [count, setCount] = useSharedState("count", 2);
console.log("comp2 rendered");
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
</div>
);
}
上述方式是可以實(shí)現(xiàn)React Query狀態(tài)管理,但是有性能問題,其實(shí)本質(zhì)還是利用Context透?jìng)?/code>,我們知道Context處理prop drilling問題,但是有性能問題,詳情可查看這篇文章 精讀《React — 5 Things That Might Surprise You》
不過令人費(fèi)解的是官方強(qiáng)調(diào)ReactQuery 的狀態(tài)管理,但是在官網(wǎng)例子并沒有給出類似的例子,上述例子還是在官方的github倉庫翻到

作者說會(huì)在一個(gè)講座分析,后面我再深入研究,先留個(gè)坑

參考文獻(xiàn)
https://react-query.tanstack.com/quick-start
https://github.com/tannerlinsley/react-query/discussions/489
https://github.com/tannerlinsley/react-query/discussions/329
https://tkdodo.eu/blog/react-query-as-a-state-manager
