在前端开发的数据请求与处理中,我们可能经常会写出这样的请求处理代码:
// 传统数据获取方式示例
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.ts 或 main.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, isError:data:接口返回的数据(已自动解析)、isLoading:是否处于加载中状态、isError:是否请求失败
基于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 })
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 用法
queryClient 是 vue-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 支持分页和无限滚动等高级功能。通过 getNextPage 和 getPreviousPage 可以管理分页请求,或者用 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 用于指定如何获取下一页的参数