异步数据的状态管理TanStack Query 使用总结
TanStack Query使用总结
TanStack Query 是一个开源、功能齐全、支持 TypeScript 的库,非常适合用于处理客户端状态,处理异步或服务器状态。它支持React,Vue,Svelte,Solid框架,大多时候我们都会我们使用的框架把它叫做vue-query或者react-query。
特点:
- 非常好用的query库,目的是为了缓存后端api的结果,不用像以前一样,手动将结果一个一个存储到store,并且提供了一些非常好用的hook方法
- 非常适合用于处理客户端状态,处理异步或服务器状态
- 默认支持异步
- 它并不是用于替代axios等请求库,而只是作为外层的封装,方便控制请求与结果
官网地址:https://tanstack.com/query/v4/docs/react/guides/query-retries
它在React,Vue这二个框架中使用的形式是一样的,除了在引入,安装的包和初始化上的不同,目前并没发现其它使用上区别。
在React,Vue使用变量去接受useQuery返回的参数就可以直接使用,或者在回调函数中对返回的数据进行处理。
vue中使用
先在main.js中引入注册
import { VueQueryPlugin } from '@tanstack/vue-query'
app.use(VueQueryPlugin);
子组件中使用
<script lang="ts" setup>
import {useQuery} from '@tanstack/vue-query'
const columns = [...]
const dataSourceQuery = useQuery(['dataSourceQuery'], () => api.getUseList())
const dataSourceVal = computed(() => {
return dataSourceQuery.data.value?.data?.list
})
</script>
<template>
<a-table :columns="columns" :dataSource="dataSourceVal" :loading="loading"></a-table>
</template>
react中使用
需要在根组件中引入QueryClient,QueryClientProvider,用QueryClientProvider对根组件进行包裹,再用
QueryClient new一个实例,将实例使用Context的方式提供给整个App
import {QueryClient,QueryClientProvider} from "@tanstack/react-query";
// 创建一个 client
const queryClient = new QueryClient();
function App() {
return (
// 提供 client 至 App
<QueryClientProvider client={queryClient}>
<Todos />
</QueryClientProvider>
);
}
子组件中使用
import { useQuery } from '@tanstack/react-query'
const dataSourceQuery = useQuery(['dataSourceQuery'], () => api.getUseList())
svelte中使用
和react中一样先对根组件进行包裹,QueryClient new一个实例,将实例使用Context的方式提供给整个App
<script lang="ts">
import {QueryClient, QueryClientProvider} from '@tanstack/svelte-query'
import Example from './lib/Example.svelte'
const queryClient = new QueryClient()
</script>
<QueryClientProvider client={queryClient}>
<Example/>
</QueryClientProvider>
子组件中使用
应用的是createQuery,vue和react引入的是useQuery
<script lang="ts">
import {createQuery} from '@tanstack/svelte-query'
let intervalMs = 1000
const endpoint = 'http://localhost:5173/api/data'
const query = createQuery({
queryKey: ['refetch'],
queryFn: async () => await fetch(endpoint).then((r) => r.json()),
refetchInterval: intervalMs,
})
</script>
Solid框架中使用
import {QueryClient, QueryClientProvider} from "@tanstack/solid-query";
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
);
}
子组件中使用
它查询键值必须包裹在一个函数内:
function Example() {
const query = createQuery(() => ['todos'], fetchTodos)
return (
<div>
<Switch>
<Match when={query.isLoading}>
<p>Loading...</p>
</Match>
<Match when={query.isError}>
<p>Error: {query.error.message}</p>
</Match>
<Match when={query.isSuccess}>
<For each={query.data}>
{(todo) => <p>{todo.title}</p>}
</For>
</Match>
</Switch>
</div>
)
}
初始化完成后,就可以使用了首先是查询 Queries
查询 Queries
至少需要二个参数
- 查询唯一键值
- 一个Promise 的函数
用例:
queryKey和要调用的promise方法可以用对象的形式包裹传入,也可以直接直接以多个参数的方式传入
import {useQuery} from '@tanstack/vue-query'
//多个参数形式传入
const dataSourceQuery = useQuery(['dataSourceQuery'], () => api.getUseList())
//参数用一个对象形式传入
const dataSourceQuery1 = useQuery({ queryKey: ["dataSourceQuery"], queryFn: api.getUseList })
查询后直接获取参数和状态
import {useQuery} from '@tanstack/vue-query'
const { isLoading, isError, data, error } = useQuery(['dataSourceQuery'], () => api.getUseList())
console.log('isLoading, isError, data, error---',isLoading, isError, data, error)
由于 Vue Query 的获取机制建立在 Promises 上,因此您可以将 Vue Query 与任何异步数据获取(包括 GraphQL)一起使用!
import {useQuery} from '@tanstack/vue-query'
const dataSourceQuery = useQuery(['dataSourceQuery'], async () => {
return await api.getUseList()
})
或者再加个promise.all,在配合一些回调函数一起来使用,比如onSuccess
import {useQuery} from '@tanstack/vue-query'
const dataSourceQuery = useQuery(['dataSourceQuery'], async () => {
return await Promise.all(promises)
},
{
onSuccess: (data) => {
console.log(data)
}
})
tanstack-query提供的一些回调函数
- onMutate 修改即将发生
- onError 错误触发
- onSuccess 请求成功
- onSettled 错误或成功
result对象包含一些非常重要的状态
-
isLoading 或者 status === 'loading' - 查询暂时还没有数据
-
isError 或者 status === 'error' - 查询遇到一个错误
-
isSuccess 或者 status === 'success' - 查询成功,并且数据可用
-
isFetching 如果是在后台获取数据,可以用这个来表示获取中的状态
-
error 如果查询处于isError状态,则可以通过error属性获取该错误
-
data 如果查询处于success状态,则可以通过data属性获得数据
-
fetchStatus 可以是fetching、paused、idle
查询的键值
Vue Query 在内部基于查询键值来管理查询缓存。 传递给 Vue Query 的查询键值必须是一个数组。
查询功能依赖于变量,则将其包含在查询键值中
import {useQuery} from '@tanstack/vue-query'
const dataSourceQuery = useQuery(['dataSourceQuery',userId], async () => {
return await api.getUseList()
})
依赖查询
使用enabled来进行依赖查询,也就是满足特定条件的时候才会执行,enabled选项可以告诉查询何时可以运行,enabled的值是true时运行,false时不运行,不添加enabled选项表示直接运行。
举例:比如useQuery这个hook想在首次加载时不执行的,可以添加enabled属性
import {useQuery} from '@tanstack/vue-query'
const userId = '11'
//多个参数形式传入
const dataSourceQuery = useQuery(
['dataSourceQuery'],() => api.getUseList(),
{
enabled: !!userId
}
)
//参数用于一个对象形式传入
const dataSourceQuery1 = useQuery({
queryKey: ["dataSourceQuery"], queryFn: ()=> api.getUseList(),enabled: !!userId
})
刷新或重新获取数据
refetch和queryClient.refetchQueries(key)都可以用来刷新数据
用例:默认不调用,点击时调用
使用依赖查询enabled,和refetch()来实现
<script setup lang="ts">
import {ref} from "vue";
import {useStore} from "../../store/home"
import {useQuery} from '@tanstack/vue-query'
const userInfoStore = useStore()
const userId = ref<number>(0)
const query = useQuery({
queryKey: ['home', userId.value],
queryFn: () => userInfoStore.randomizeCounter(userId.value),
enabled: !!userId.value
})
const handleSearch = (val: number) => {
userId.value = val + userId.value
query.refetch()
};
</script>
<template>
<button @click="handleSearch(1)">查询</button>
<h1>{{ userInfoStore.count }}</h1>
</template>
queryClient.refetchQueries(key)实现刷新
要先引入useQueryClient(),再通过queryKey去指定要刷新的query请求
<script setup lang="ts">
import {ref} from "vue";
import {useStore} from "../../store/home"
import {useQuery} from '@tanstack/vue-query'
const userInfoStore = useStore()
const userId = ref<number>(0)
const queryClient = useQueryClient()
const query = useQuery({
queryKey: ['home', userId.value],
queryFn: () => userInfoStore.randomizeCounter(userId.value),
enabled: !!userId.value
})
const handleSearch = (val: number) => {
userId.value = val + userId.value
queryClient.refetchQueries('home')
};
</script>
<template>
<button @click="handleSearch(1)">查询</button>
<h1>{{ userInfoStore.count }}</h1>
</template>
查询重试
当useQuery查询失败(查询函数引发错误)时,如果该查询的请求未达到最大连续重试次数(默认 3 次
-
设置retry = false将禁用重试
-
设置retry = 6该函数抛出最终错误前,将重试 6 次
-
设置retry = true将无限次重试失败的请求
-
设置retry =(failureCount,error)=> ...允许基于请求失败的原因进行自定义逻辑
import {useQuery} from '@tanstack/vue-query'
//多个参数形式传入
const dataSourceQuery = useQuery(['dataSourceQuery'], () => api.getUseList(),{enabled: !!userId,retry: 6})
//对象形式传入
const dataSourceQuery1 = useQuery({
queryKey: ["dataSourceQuery"], queryFn: ()=> api.getUseList(),enabled: !!userId,retry: 6
})
重试延迟
默认情况下,React Query 不会在请求失败后立即重试。按照标准,后退延迟将逐渐应用于每次重试。
默认的retryDelay设置为以二的倍数递增(从1000ms开始),但不超过 30 秒:
为所有查询配置
import { VueQueryPlugin } from "@tanstack/vue-query";
const vueQueryPluginOptions = {
queryClientConfig: {
defaultOptions: {
queries: {
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
},
},
},
};
app.use(VueQueryPlugin, vueQueryPluginOptions);
单个查询设置
const dataSourceQuery = useQuery(['dataSourceQuery'], () => api.getUseList(),{
retryDelay: 1000, // 无论重试多少次,都会始终等待1000毫秒然后重试
})
分页/滞后查询
常用分页的UI会在success和loading状态之间来回跳转,因为每个新页面都被视为一个全新的查询。这种体验并不是最佳的,
Vue Query 带有一个称为keepPreviousData的强大功能,从而使得这个问题可以被轻易的解决。
通过将keepPreviousData设置为true,我们可以得到一些新的东西:
-
请求新数据时,即使查询键值已更改,上次成功获取的数据仍可用
-
当新数据到达时,先前的数据将被无缝交换以显示新数据
-
可以使用isPreviousData来了解当前查询提供的是什么数据
const dataSourceQuery = useQuery(['dataSourceQuery'], () => api.getUseList(),{
keepPreviousData: true,
})
缓存数据
如果该query已经在缓存中则不会执行,可以设置一个staleTime来指定时间。
staleTime:表示数据多久才会过期,同时也决定了什么时候向服务端发送请求更新数据,默认值是 0,也就是在每次执行的 query 中都会发送请求来更新数据。
有两种配置方式:全局配置,局部设置:
全局配置
import {useQuery, QueryClient} from '@tanstack/vue-query'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 15, // 15秒数据才过期
},
},
});
const prefetchTodos = async () => {
// 该查询的结果将像普通查询一样被缓存
await queryClient.prefetchQuery(["dataSourceQuery"], api.getUseList);
};
//调用方法间隔15秒才会重新请求接口
function handClick() {
prefetchTodos()
console.log('res------', queryClient.getQueryData(['dataSourceQuery']))
}
局部设置
// B:批量局部设置
queryClient.setQueryDefaults(["dataSourceQuery"], { staleTime: 1000 * 60 }); // 60秒数据设置成过期
// C:对某一个Query设置
const useTodos = useQuery(["dataSourceQuery"], api.getUseList, { staleTime: 1000 * 60 });
全局配置(A)的含义就是:在 15 秒内的请求,都认为该 query 的数据没有过期,这个数据可以拿过来直接用,所以不需要发送请求,如果超过 15 秒内的请求,表示这个数据是过期的,所以需要重新请求接口拉取最新的数据。
局部配置(B、C)就把这个时间拉的更长了,只有超过 60 秒之后才会发送请求更新缓存数据。对于一些稳定的接口数据可以试试设置这个值,以达到缓存数据的效果,避免过多的请求。
按键值获取缓存结果
queryClient.getQueryData(['dataSourceQuery'])
添加或更新查询的缓存结果,直接按键值添加或更新查询的缓存结果。
queryClient.setQueryData(["dataSourceQuery"], api.getUseList);
invalidateQueries使缓存的数据过期
queryClient.invalidateQueries() // 过期所有
queryClient.invalidateQueries(['dataSourceQuery']) // 过期指定的key
// 下面的都会失效
const todoListQuery = useQuery(['dataSourceQuery'], fetchTodoList)
修改 Mutations
与查询不同,修改通常意味着用于创建/更新/删除数据或执行服务器命令等副作用。 为此,React Query 导出了useMutation hook。
const { isLoading, isError, error, isSuccess } = useMutation({
mutationFn: () => api.getUseList(),
});
可以通过返回的状态来判断是否修改成功,或者是通过回调来处理数据
以下是TanStack Query提供的状态
- isIdle 或 status === 'idle' - 修改目前处于闲置状态或处于全新/重置状态
- isLoading 或 status === 'loading' - 修改目前正在进行操作
- isError 或 status === 'error' - 修改遇到了错误
- isSuccess 或 status === 'success' - 修改是成功的,且数据可用
回调函数的使用
useMutation(addTodo, {
onMutate: (variables) => {
// 修改即将发生!
// (可选)返回包含回滚时使用的数据的上下文
return { id: 1 };
},
onError: (error, variables, context) => {
// 错误触发!
console.log(`rolling back optimistic update with id ${context.id}`);
},
onSuccess: (data, variables, context) => {
// Boom baby!
},
onSettled: (data, error, variables, context) => {
// 错误或成功……这并不重要
},
});