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

接下來,就是引出今天的主角 React Query
React Query
React Query 通常被描述為 React 缺少的數(shù)據(jù)獲取(data-fetching)庫,但是從更廣泛的角度來看,它使 React 程序中的獲取,緩存,同步和更新服務器狀態(tài)變得輕而易舉。
解決了什么問題
服務端狀態(tài)有以下特點:
存儲在遠端,本地無法直接控制
需要異步 API 來查詢和更新
可能在不知情的情況下,被另一個請求方更改了數(shù)據(jù),導致數(shù)據(jù)不同步
現(xiàn)有的狀態(tài)管理庫(如 Mobx、Redux等)適用于管理客戶端狀態(tài),但它們并不關心客戶端是如何異步請求遠端數(shù)據(jù)的,所以他們并不適合處理異步的、來自服務端的狀態(tài)。
而 React Query 就是為了解決服務端狀態(tài)帶來的上述問題而出現(xiàn)的,除此之外它還帶來了以下特性:
更方便地控制緩存
把對于相同數(shù)據(jù)的多個請求簡化成一個
在后臺更新過期數(shù)據(jù)
知道數(shù)據(jù)什么時候會「過期」
對于數(shù)據(jù)的變化盡可能快得做出響應
分頁查詢和懶加載等請求性能優(yōu)化
管理服務器狀態(tài)的內(nèi)存和垃圾回收
通過結(jié)構(gòu)共享(structural sharing)來緩存查詢結(jié)果
請求中間態(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會自動把這些isLoading,isError請求中間態(tài)處理好,我們不必寫重復邏輯,另外配合Suspense提對一點對于loading場景的處理,Suspense也支持的不錯,特別是局部Loading,簡直Nice!
ReactQuery 的狀態(tài)管理
Fetch, cache and update data in your React and React Native applications all without touching any "global state".
官網(wǎng)對于React Query的簡述,注意global state,你會不解,為什么React Query明明是一個請求庫,跟數(shù)據(jù)狀態(tài)管理又有什么關系,甚至可以處做全局狀態(tài)管理
那是因為ReactQuery 會在全局維護一個服務端狀態(tài)樹,根據(jù) Query key 去查找狀態(tài)樹中是否有可用的數(shù)據(jù),如果有則直接返回,否則則會發(fā)起請求,并將請求結(jié)果以 Query key 為主鍵存儲到狀態(tài)樹中。
ReactQuery 就將我們所有的服務端狀態(tài)維護在全局,并配合它的緩存策略來執(zhí)行數(shù)據(jù)的存儲和更新。借助于這樣的特性,我們就可以將所有跟服務端進行交互的數(shù)據(jù)從類似于 Redux 這樣的狀態(tài)管理工具中剝離,而全部交給 ReactQuery 來管理。
舉個例子:
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>
);
}
上述方式是可以實現(xiàn)React Query狀態(tài)管理,但是有性能問題,其實本質(zhì)還是利用Context透傳,我們知道Context處理prop drilling問題,但是有性能問題,詳情可查看這篇文章 精讀《React — 5 Things That Might Surprise You》
不過令人費解的是官方強調(diào)ReactQuery 的狀態(tài)管理,但是在官網(wǎng)例子并沒有給出類似的例子,上述例子還是在官方的github倉庫翻到

作者說會在一個講座分析,后面我再深入研究,先留個坑

參考文獻
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
