最近团队中用了react-query,了解了一下之后确实挺好玩的,简单记录一下
在前端平时的开发中,可以把我们需要维护的状态分为两类:
react-query能够更好的帮助我们处理第二类状态。服务端状态通常会存在组件中,如果要复用,要存在全局状态如redux中。但这样有两个问题:
而react-query可以无痛的完全替我们处理这些问题:
代码如下(示例):
import { useQuery } from 'react-query';
代码如下(示例):
// 通过useQuery请求数据,hook返回的data和isLoding都可以直接使用
// 除了data,isLoading,还有其他返回如 status,error,isFetching等等
const { data, isLoading } = useQuery(
// useQuery的第一个参数:查询的唯一key,要求必须是可序列化的
['statData', { date, group }],
// useQuery的第二个参数:查询函数,要求必须是返回Promise的函数
// 在查询函数中,也可以提取到key中的参数,比如
// const [_key, { date, department }] = queryKey;
async () => {
const { data: dataObj } = await fetchStatData({
year: date.getFullYear(),
month: date.getMonth(),
deptId: group ? group.id : '',
});
return dataObj;
},
// useQuery的第三个参数:配置对象config
{
// 当团队id存在时才能发起请求
enabled: !!(group && group.id),
}
);
最佳实践:
['statData', { date, department }]
而非['statData', date, department]
。使用对象,不管对象中键值的顺序如何,都会被认为是相等的,react-query才能准确的复用他们throw new Error('Oh no')
代码如下(示例):
// 列表数据
const {
data, // 数据
fetchNextPage, // 请求下一页的方法
hasNextPage, // 是否还有下一页
isLoading, // loading
} = useInfiniteQuery(
// 参数1:query key
['statisticsByMonthList', { date, group }],
// 参数2:query function
async ({ pageNum = 1 }) => {
const { data: dataObj } = await fetchList({
year: date.getFullYear(),
month: date.getMonth(),
groupId: group ? group.id : '',
pageNum,
pageSize: 10,
});
// 数据处理并返回
// 下边的getNextPageParam的入参lastPage就是这个
return {
list: dataObj?.list ?? [],
pageNum, // 记录当前页码
total: dataObj?.total,
hasNextPage: dataObj?.hasNextPage, // 计算出是否有下一页
};
},
// 配置
{
// 团队id存在才能请求
enabled: !!(group && group.id),
// 获取fetchNextPage函数的入参,因为query function只有一个入参pageNum
// 所以这里返回下一页的页码即可
getNextPageParam: (lastPage) => {
if (lastPage?.hasNextPage) {
return lastPage.pageNum + 1; // 计算下一页页码
}
},
}
);
注意理解:
useQuery
和useInfiniteQuery
即可{ queryKey, queryFn,queryConfig }
enable
,即可告诉b,当a获取结果,得到id之后再运行const isFetching = useIsFetching();
钩子,作为全局加载指示器query config 中的enable 为 false 时:
平时的分页,默认展示第一页,翻第二页会请求一次,这时翻第一页又会请求一次,因为每个新页面都被视为一个全新的查询。
对于分页查询,让uerQuery的config,增加配置:keepPreviousData: true,
,此时:
比如请求一个博客列表,当新用户的列表为空,写一个placeholderData,能够在列表为空时,展示一个示例的博客的mock数据
使用mutations去修改状态,注意,因为 mutation.mutate是异步方法, 在React16及之前的版本,由于React事件池的限制,使用时要用同步方法包裹,不能直接放在 onSubmit上
之前说了,query获取的数据会被缓存,但缓存什么时候失效呢?我们可以通过:
// 使缓存中的每个查询都无效
queryClient.invalidateQueries();
// 无效每个查询键值以 `todos` 开头的查询
queryClient.invalidateQueries("todos");
手动的让缓存失效,失效后该query会被标记为过时,再次使用时该query会在后台重新获取数据
例如:在增加todo的mutation的回调中让列表失效,这样,addTodo之后,就会重新获取列表数据
import { useMutation, useQueryClient } from "react-query";
const queryClient = useQueryClient();
// 当此修改成功时,将所有带有`todos`和`reminders`查询键值的查询都无效
const mutation = useMutation(addTodo, {
onSuccess: () => {
queryClient.invalidateQueries("todos");
queryClient.invalidateQueries("reminders");
},
});
当我们更新一个数据对象时,新的数据通常会在更新成功后返回,此时我们可以通过queryClient.setQueryData
方法,在前端直接更新对象,从而避免重新再请求一次
const queryClient = useQueryClient();
// 更新成功回调中,setQueryData,在todo query中找到id为5的
const mutation = useMutation(editTodo, {
onSuccess: (data) => {
queryClient.setQueryData(["todo", { id: 5 }], data);
},
});
mutation.mutate({
id: 5,
name: "Do the laundry",
});
// 下面的查询将使用成功的更新响应来进行更新
const { status, data, error } = useQuery(["todo", { id: 5 }], fetchTodoByID);
常用功能就是这些,具体的细节还是需要在开发的过程中查文档