大家好,我是石小石!
在前端开发的数据请求与处理中,我们可能经常会写出这样的请求处理代码:
// 传统数据获取方式示例
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
:是否请求失败
除了data, isLoading, isError等参数,useQuery的更多解构参数如下
参数名称 | 类型 | 描述 |
---|---|---|
data | any | 请求成功返回的数据 |
isLoading | boolean | 请求是否正在加载中 |
isError | boolean | 请求是否出现错误 |
isSuccess | boolean | 请求是否成功 |
isFetching | boolean | 是否正在重新获取数据 |
error | any | 请求失败时的错误信息 |
refetch | () => void | 手动触发查询的函数 |
dataUpdatedAt | number | 上次更新数据的时间戳 |
errorUpdatedAt | number | 上次错误信息的更新时间戳 |
isIdle | boolean | 查询是否处于空闲状态(通常是没有启动查询或被禁用时) |
enabled | boolean | 控制查询是否自动执行。false 时不会自动执行,直到手动触发。 |
staleTime | number | 数据在多少毫秒内被认为是“新鲜的”,过期后重新请求数据 |
cacheTime | number | 查询缓存的最长时间,在此时间内,如果查询没有被使用,数据会被清除 |
select | (data: any) => any | 用于从返回的数据中选择需要的数据,可以进行数据处理 |
onSuccess | (data: any) => void | 查询成功时执行的回调函数 |
onError | (error: any) => void | 查询失败时执行的回调函数 |
cancel | Function | 取消请求 |
基于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
返回一个对象,其中包含了以下状态和方法:
属性名 | 类型 | 说明 | 示例 |
---|---|---|---|
mutate | Function | 执行 mutation 操作的函数,触发数据修改、提交等操作。 | mutate({ title: '新的任务', completed: false }) |
isLoading | Boolean | 表示 mutation 操作是否正在加载。 | if (isLoading) { return '加载中...'; } |
isError | Boolean | 表示 mutation 操作是否失败。 | if (isError) { return '操作失败'; } |
isSuccess | Boolean | 表示 mutation 操作是否成功执行。 | if (isSuccess) { return '操作成功'; } |
isIdle | Boolean | 表示 mutation 是否处于初始状态(即没有开始执行时)。 | if (isIdle) { return '等待操作'; } |
data | any | mutation 成功后的返回数据。 | console.log(data); |
error | any | mutation 失败时的错误信息。 | console.log(error); |
reset | Function | 重置 mutation 状态,通常用于表单重置等操作。 | reset() |
variables | any | 传递给 mutationFn 的变量。 | mutationFn({ title: '任务' }) |
context | any | mutation 执行时的上下文对象,通常通过 onMutate 返回,方便进行乐观更新。 | const context = onMutate() => ({ previousTodos }) |
mutateAsync | Function | 异步的 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
用法
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 用于指定如何获取下一页的参数