在项目中,通常都需要跟服务端进行异步的数据交互,基本都是用到axios这个库来做请求,嗯,毕竟拥有80k star
,明星项目
接下来,我们来回顾下axios在项目中的使用
以查询用户信息为例,我们会这样封装
async function requestUsers(){
const {data} =await axios.get('/api/users');
return data;
}
我们再用hooks再封装下这个请求,包括loading等中间态的封装,处理的优雅一点
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>
)
}
可以看到,我们的项目中基本上是这样封装请求,我们不仅要请求数据,还要处理相应的loading,error这些中间态,这类通用的中间状态处理逻辑可能在不同组件中重复写很多次。
另外,现在的前端项目特别是单页面应用,会使用Flux、Redux、Mobox等状态管理库,会把组件间共享的数据都存放在状态管理库中,这些可以分为两类,一类是用户交互的中间状态,比如isLoading,isClose,modalVisible
等等,另外一类就是服务端状态(数据)
我们一般处理的方式都是无差别的存放在全局状态管理上,状态管理库为了兼容异步请求,就有了redux-saga,redux-action
这些异步解决方案
其实对于redux
等状态管理库,本身是没有异步这个概念,只有mutation这种操作,为了支持异步,硬是强加了异步action这种操作,实际这些异步中间件就是在最后的请求回调透传了dispatch
,诸如这些情况,我们不仅将数据一锅炖放在全局状态管理上,写法上也使得项目越来越臃肿了(以至于出现后面rematch、dva方案进行简化),我们有没有想过,服务端的状态就不应该放在全局状态管理上,全局状态管理应该专门处理用户交互的中间状态
接下来,就是引出今天的主角 React Query
React Query 通常被描述为 React 缺少的数据获取(data-fetching)库,但是从更广泛的角度来看,它使 React 程序中的获取,缓存,同步和更新服务器状态变得轻而易举。
服务端状态有以下特点:
现有的状态管理库(如 Mobx、Redux等)适用于管理客户端状态,但它们并不关心客户端是如何异步请求远端数据的,所以他们并不适合处理异步的、来自服务端的状态。
而 React Query 就是为了解决服务端状态带来的上述问题而出现的,除此之外它还带来了以下特性:
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请求中间态处理好,我们不必写重复逻辑,另外配合Suspense
提对一点对于loading场景的处理,Suspense
也支持的不错,特别是局部Loading,简直Nice!
Fetch, cache and update data in your React and React Native applications all without touching any "global state".
官网对于React Query的简述,注意global state
,你会不解,为什么React Query明明是一个请求库,跟数据状态管理又有什么关系,甚至可以处做全局状态管理
那是因为ReactQuery 会在全局维护一个服务端状态树,根据 Query key 去查找状态树中是否有可用的数据,如果有则直接返回,否则则会发起请求,并将请求结果以 Query key 为主键存储到状态树中。
ReactQuery 就将我们所有的服务端状态维护在全局,并配合它的缓存策略来执行数据的存储和更新。借助于这样的特性,我们就可以将所有跟服务端进行交互的数据从类似于 Redux
这样的状态管理工具中剥离,而全部交给 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>
);
}
上述方式是可以实现React Query状态管理,但是有性能问题,其实本质还是利用Context透传
,我们知道Context处理prop drilling
问题,但是有性能问题,详情可查看这篇文章 精读《React — 5 Things That Might Surprise You》
不过令人费解的是官方强调ReactQuery 的状态管理
,但是在官网例子并没有给出类似的例子,上述例子还是在官方的github仓库翻到
作者说会在一个讲座分析,后面我再深入研究,先留个坑