vue3中超赞的请求数据管理方案vue-query !

29 阅读6分钟

大家好,我是石小石!


在前端开发的数据请求与处理中,我们可能经常会写出这样的请求处理代码:

// 传统数据获取方式示例
const loading = ref(false)
const error = ref(null)
const data = ref([])

const fetchData = async () => {
  try {
    loading.value = true
    const res = await axios.get('/api/data')
    data.value = res.data
  } catch (err) {
    error.value = err
  } finally {
    loading.value = false
  }
}
// 需要手动管理加载状态、错误处理、缓存...

代码并没有问题,只是这种固定模式的代码在很多场景下写起来过于繁琐,vue-query 完美的解决了这个问题。

vue-query 是什么?

@tanstack/vue-query 是流行的 React Query 在 Vue 上的实现版本。它不是网络请求库的替代品(如 axios 或 fetch),而是对网络请求结果的 缓存、状态管理、自动刷新、重试机制 等进行了统一封装。它具有以下优势:

  • 自动缓存和数据同步
  • 请求失败自动重试
  • 页面聚焦自动重新请求数据
  • 请求状态自动管理(加载中、错误、成功)
  • 支持分页、无限加载等高级功能
  • 代码更简洁、逻辑更清晰

借助 Vue Query,我们可以专注于“数据如何使用”,而不用操心“数据从哪里来、何时来、状态如何”等细节问题,从而大幅提升前端开发的体验与效率。

安装与初始化

npm install @tanstack/vue-query

安装依赖后,在 main.tsmain.js 中配置 VueQuery即可使用

核心 API 详解与实战

useQuery-数据请求

useQuery是获取数据的核心方法

<script setup>
import { useQuery } from '@tanstack/vue-query'

const { data, isLoading, isError } = useQuery({
  queryKey: ['todos'], // 唯一缓存标识
  queryFn: () => axios.get('/api/todos').then(res => res.data)
})
</script>

<template>
  <div v-if="isLoading">加载中...</div>
  <div v-else-if="isError">出错了!</div>
  <ul v-else>
    <li v-for="todo in data" :key="todo.id">{{ todo.title }}</li>
  </ul>
</template>

上述代码使用 @tanstack/vue-query 实现了异步获取待办列表(todos)数据,它的核心参数如下:

  • queryKey: ['todos']
    每个查询都需要一个唯一的 key,用于缓存标识(同样的 key 会共享缓存)。
  • queryFn: () => axios.get(...)
    实际的数据请求函数,返回一个 Promise,结果会被自动缓存。
  • 解构的 data, isLoading, isErrordata:接口返回的数据(已自动解析)、isLoading:是否处于加载中状态、isError:是否请求失败

除了data, isLoading, isError等参数,useQuery的更多解构参数如下

参数名称类型描述
dataany请求成功返回的数据
isLoadingboolean请求是否正在加载中
isErrorboolean请求是否出现错误
isSuccessboolean请求是否成功
isFetchingboolean是否正在重新获取数据
errorany请求失败时的错误信息
refetch() => void手动触发查询的函数
dataUpdatedAtnumber上次更新数据的时间戳
errorUpdatedAtnumber上次错误信息的更新时间戳
isIdleboolean查询是否处于空闲状态(通常是没有启动查询或被禁用时)
enabledboolean控制查询是否自动执行。false时不会自动执行,直到手动触发。
staleTimenumber数据在多少毫秒内被认为是“新鲜的”,过期后重新请求数据
cacheTimenumber查询缓存的最长时间,在此时间内,如果查询没有被使用,数据会被清除
select(data: any) => any用于从返回的数据中选择需要的数据,可以进行数据处理
onSuccess(data: any) => void查询成功时执行的回调函数
onError(error: any) => void查询失败时执行的回调函数
cancelFunction取消请求

基于useQuery的解构属性,我们可以实现以下高级功能:

  • 自动刷新和缓存时间
useQuery({
  queryKey: ['posts'],
  queryFn: fetchPosts,
  refetchOnWindowFocus: true, // 页面切回自动刷新
  staleTime: 1000 * 60 * 5     // 数据 5 分钟内不重新获取
})
  • 条件查询
useQuery({
  queryKey: ['user', userId],
  queryFn: () => fetchUser(userId),
  enabled: !!userId   // userId 存在才执行
})
  • 手动刷新
const { refetch } = useQuery({
  queryKey: ['posts'],
  queryFn: fetchPosts
})

refetch() // 手动重新获取数据
  • 取消请求
const { data,cancel } = useQuery({
  queryKey: ['todos'],
  queryFn: ()=>{},
});

// 取消请求
const cancelRequest = () => {
  cancel();
};
  • 数据共享

使用相同的 key,我们可以在其他组件共享数据

<script setup>
import { useQuery } from '@tanstack/vue-query'

const { data } = useQuery({ queryKey: ['todos'] }) // 使用相同的 key,共享数据
</script>

<template>
  <div>共有 {{ data?.length || 0 }} 个待办</div>
</template>

useQuery 是为了 获取数据 设计的,它会根据查询键(queryKey)缓存数据,并且会自动执行并更新状态。使用 POST 请求时提交或修改数据时,我们通常使用useMutation。

  • 请求合并

useQuery 默认会在短时间内合并对同一个 queryKey 的多个请求。这意味着,若有多个组件请求相同的数据,vue-query 会合并请求并且只发出一次请求。这有助于避免冗余的请求。

使用 useMutation 执行副作用操作

useMutation 是 React Query 用于执行 创建、修改、删除等副作用操作(例如 POST、PUT、DELETE 请求)的 Hook。它与 useQuery 不同,后者主要用于获取数据和缓存。useMutation 专注于发起请求、更新数据或提交数据时的状态管理。

import { useMutation } from '@tanstack/vue-query'
import axios from 'axios'

const { mutate, isLoading, isError, data, error, isSuccess } = useMutation({
  mutationFn: (newTodo) => axios.post('/api/todos', newTodo),
  onSuccess: (data) => {
    console.log('提交成功', data)
  },
  onError: (error) => {
    console.log('提交失败', error)
  }
})

// 调用方式
mutate({ title: '学习Vue-Query', completed: false })

useMutation 返回一个对象,其中包含了以下状态和方法:

属性名类型说明示例
mutateFunction执行 mutation 操作的函数,触发数据修改、提交等操作。mutate({ title: '新的任务', completed: false })
isLoadingBoolean表示 mutation 操作是否正在加载。if (isLoading) { return '加载中...'; }
isErrorBoolean表示 mutation 操作是否失败。if (isError) { return '操作失败'; }
isSuccessBoolean表示 mutation 操作是否成功执行。if (isSuccess) { return '操作成功'; }
isIdleBoolean表示 mutation 是否处于初始状态(即没有开始执行时)。if (isIdle) { return '等待操作'; }
dataanymutation 成功后的返回数据。console.log(data);
erroranymutation 失败时的错误信息。console.log(error);
resetFunction重置 mutation 状态,通常用于表单重置等操作。reset()
variablesany传递给 mutationFn 的变量。mutationFn({ title: '任务' })
contextanymutation 执行时的上下文对象,通常通过 onMutate返回,方便进行乐观更新。const context = onMutate() => ({ previousTodos })
mutateAsyncFunction异步的 mutate方法,返回一个 Promise。await mutateAsync({ title: '任务' })

mutate 是触发 mutation 操作的函数,传入的参数会被传递到 mutationFn 中,启动数据提交操作。

const { mutate } = useMutation({
  mutationFn: (newTodo) => axios.post('/api/todos', newTodo)
})

// 调用 mutate 提交新任务
mutate({ title: '新的任务', completed: false })

data 是 mutation 成功后返回的数据。你可以通过它来获取 API 返回的结果,并在 UI 中展示。

console.log('任务提交成功:', data)

reset 用于重置 mutation 的状态。通常在表单提交成功后,或者用户手动触发重置时使用。

const { reset } = useMutation({
  mutationFn: (newTodo) => axios.post('/api/todos', newTodo)
})

// 提交成功后重置状态
reset()

queryClient 用法

queryClientvue-query 的核心对象,它用于全局管理缓存、请求重试、数据失效等。可以通过 queryClient 来执行诸如缓存清除、查询失效、手动触发查询等操作。

手动触发查询

import { useQueryClient } from '@tanstack/vue-query';

const queryClient = useQueryClient();
const { data } = useQuery(['todos'], fetchTodos);

// 通过 queryClient 手动触发查询
queryClient.refetchQueries(['todos']);

失效查询(Invalidate) invalidateQueries 用于标记某个查询为失效,之后会重新请求数据。

// 手动失效某个查询
queryClient.invalidateQueries(['todos']);

移除缓存(Remove Query Cache) 如果你希望从缓存中删除某个查询的数据,可以使用 removeQueries

queryClient.removeQueries(['todos']);

清空缓存(Clear Cache) clear() 会清除所有缓存的查询。

queryClient.clear();

分页和无限加载

useQuery 支持分页和无限滚动等高级功能。通过 getNextPagegetPreviousPage 可以管理分页请求,或者用 useInfiniteQuery 实现无限加载。

const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery(
  'todos',
  fetchTodos, {
    getNextPageParam: (lastPage, allPages) => lastPage.nextCursor,
  }
);

if (isFetchingNextPage) {
  return '加载更多...';
}

return (
  <>
    {data.pages.map((page) =>
      page.todos.map((todo) => <Todo key={todo.id} todo={todo} />)
    )}
    {hasNextPage && <button onClick={() => fetchNextPage()}>加载更多</button>}
  </>
);

useInfiniteQuery 用于处理分页请求,getNextPageParam 用于指定如何获取下一页的参数