react-query 维护服务器的处理状态

来源

按照来源,前端有两类「状态」需要管理:

  1. 用户交互的中间状态
    简单的用 useStatue useContext 进行管理,复杂的用 react redux
  2. 服务端状态
    可以自己封装 hooks 进行处理,但是涉及多余请求合,并缓存,重连,重试等状态就比较复杂了

Tanner Linsley 开发的好用的处理服务器数据状态的 hooks
react-query 是专门做服务端状态处理的不但可以应对 api 数据也可以应对 graphql

中文文档
英文文档

数据的CRUD由2个hook处理:

  • useQuery 处理数据的查
  • useMutation 处理数据的增/删/改

缓存

SWR(stale-while-revalidate)

缓存表示的是当将当前请求成功的数据缓存起来,在组件重新加载的时候,如果有缓存数据,会优先返回缓存数据,然后在背后发送新请求
缓存失效的时间是当一个 query key 的数据没有挂载点或没有观察者(界面上没有引用)的时候,开始进行计时, 时间到这个 query key
就会在缓存内删除,如果在这段时间内 query key 有了新的挂载点,缓存就不会失效

保鲜的失效的开始计算时间是跟随缓存失效倒计时的开始时间的,同时保鲜的时间段是小于等于缓存的时间段的

有俩个参数可以设置

  • cacheTime 缓存时间
    如果数据在缓存时间内,判断 staleTime 是否大于0 ,如果大于 0 进行 staleTime 判断,否则返回缓存数据,在进行数据查询,
    之后用查询回来的数据更新界面及缓存 ,如果数据不在缓存时间内,立即进行查询

    此数值默认为5分钟

  • staleTime 数据保鲜时间
    如果数据还在缓存时间里,则判断数据是否在保鲜时间内,如果在的话,就不会发起查询,直接用缓存数据,不在的话先返回缓存数据,在进行数据查询,
    之后用查询回来的数据更新界面及缓存

    此数值默认为0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// 设计巧妙的 返回 promise 函数模拟查询服务器数据
function getName(): Promise<string> {
return new Promise((resolve, reject) => {
//返回一个随机的 bool 变量
var rand = Boolean(Math.round(Math.random()));
// 返回一个随机的时间,模拟网络延时
const time = Math.random() > 0.5 ? 3000 : 500;

//随机的返回一个 reject 或 resolve
if (rand === false) {
setTimeout(() => {
console.log("发生错误");
reject(new Error("发生了错误"));
}, time);
} else {
setTimeout(() => {
console.log("获取数据成功");
resolve(Mock.mock("@name"));
}, time);
}
});

//用来展示数据的组件
function FuncComp({ name}: { name: string}) {
console.log(name + ":rending");

// useQuery 查询
const { data, error, isLoading, isError, isFetching, refetch } = useQuery(["key1"], () => getName(), {
refetchOnWindowFocus: false,
// 数据保鲜期,在保鲜期内不会去查询数据
staleTime: 3 * 1000,
//数据的缓存时间
cacheTime: 15 * 1000,
});

return (
<div>
{isLoading && <> {name}:Loading...</>} <br/>
{isFetching && <>{name}:Fetching...</>} <br/>
{isError && (<>{name}:{(error as Error).message} </>)} <br/>
{data && (<>{name}:{data}</>)} <br/>
<br/>

<button
// 强制刷新数据
onClick={() => refetch()}>
{name}+:refetch
</button>
</div>
);
}
}

// 通过 toggle 来显示或卸载组件,来测试 SWR 的概念
// FuncComp 卸载的时间超过 cacheTime ,在此显示组件缓存数据会消失
// FuncComp 卸载的时间不超过 cacheTime,在 staleTime 内不会发起新的查询,否则会发起新的查询,无论如何都会先返回缓存的数据
function App() {
const [status, { toggle }] = useToggle()
return (
<>
{status && <FuncComp name="组件A" para={true} ></FuncComp>}
<br/>
<button onClick={toggle}>显示组件 FuncComp</button>
</>
);
}

只要有挂载点或观察者缓存永远不会失效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 // 对上个例子的组件用如下方式使用
// 无论任何时候 query 都有一个挂载点或观察者,那 缓存就不可能会失效,那么数据就一直是处于保鲜状态的,
// 无论如何切换组件,显示的数据就不会更新,除非手动的更新数据或手动的使缓存数据失效

// 在一个组件内强制 refetch 数据,切换回另一个组件的时候,界面也会更新,这个就是状态管理的全局性
function App() {
const [status, { toggle }] = useToggle()
return (
<>
{status ? <FuncComp name="组件A" para={true} ></FuncComp>:<FuncComp name="组件B" para={true} ></FuncComp>}
<br/>
<button onClick={toggle}>显示组件 FuncComp</button>
</>
);
}

query key 内包含状态数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function FuncComp({ name}: { name: string}) {
console.log(name + ":rending");

const [status, setStatus] = useState(false);
const { data, error, isLoading, isError, isFetching, refetch } = useQuery(["key1", status], () => getName(), {
refetchOnWindowFocus: false,
// 数据保鲜期,在保鲜期内不会去查询数据
staleTime: 15 * 1000,
//数据的缓存时间
cacheTime: 15 * 1000,
});

return (
<div>
{isLoading && <> {name}:Loading...</>} <br/>
{isFetching && <>{name}:Fetching...</>} <br/>
{isError && (<>{name}:{(error as Error).message} </>)} <br/>
{data && (<>{name}:{data}</>)} <br/>
<br/>
<button
// 可以通过改变 queryKey 重新获取数据
onClick={() => { setStatus((x) => !x); }}
>
{name}:click_{String(status)}
</button>

<button onClick={() => refetch()}>
{name}+:refetch
</button>
</div>
);
}