异步数据的状态管理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) => {
    // 错误或成功……这并不重要
  },
});
posted @ 2023-02-25 00:36  雪旭  阅读(2339)  评论(0编辑  收藏  举报