一、 什么是 Vue Query?
Vue Query 是 TanStack Query 针对 Vue.js 框架提供的官方适配库 (@tanstack/vue-query
)。TanStack Query 本身是一个功能强大的、框架无关 的 JavaScript/TypeScript 库,专门用于获取、缓存、同步和更新 Web 应用中的异步数据状态 ,尤其是与服务器状态交互相关的状态。
简单来说,Vue Query 让你能够以一种极其高效、声明式且几乎无需手动配置的方式来管理来自 API 或其他异步来源的数据。
二、 为什么需要 Vue Query?它解决了哪些痛点?
在 Vue 应用中,我们通常使用 axios
或 fetch
来获取数据。但这仅仅是第一步,后续管理这些数据状态会带来一系列挑战:
- 手动缓存逻辑: 如何避免对相同数据反复发起请求?如何管理缓存有效期?通常需要自己实现复杂的缓存机制。
- 加载与错误状态处理: 每个请求都需要手动管理
isLoading
,error
,data
等状态,导致大量重复的模板代码 (v-if="isLoading"
,v-if="error"
等)。 - 数据同步: 服务器数据可能随时更新,如何让客户端数据保持最新?需要手动实现轮询、窗口聚焦时重新获取等逻辑。
- 陈旧数据 (Stale Data): 如何在显示可能过时的数据以快速响应的同时,在后台更新数据?
- 请求去重: 短时间内对同一资源的多个组件请求,如何确保只发送一次网络请求?
- 分页与无限滚动: 实现这些常见 UI 模式的状态管理非常繁琐。
- 数据变更与缓存更新: 当你通过 POST/PUT/DELETE 修改了数据后,如何智能地让相关的列表或详情数据自动更新或重新获取?
- 乐观更新: 如何在提交变更后立即更新 UI 以提升体验,并在失败时自动回滚?
传统的 Vue 状态管理库(如 Pinia, Vuex)主要设计用于管理客户端状态(UI 状态、表单数据等),用它们直接管理服务器状态需要编写大量异步 action、手动处理缓存和同步逻辑,不够优雅且效率不高。
三、 Vue Query 的核心理念
Vue Query 将来自服务器的数据视为一种外部状态 ,客户端并不完全拥有它。客户端的核心任务是与这个状态进行同步 ,并在本地进行高效的缓存和管理。
四、 核心概念与功能
-
查询 (Queries):
useQuery
-
用途: 从异步源(通常是 API)获取 (读取) 数据。
-
基本用法:
typescriptimport { useQuery } from '@tanstack/vue-query'; import axios from 'axios'; // 在 setup 函数或 <script setup> 中使用 const { data: todos, // 获取的数据,响应式 ref status, // 'pending', 'error', 'success' isLoading, // 是否首次加载 (v5 前常用) isPending, // 是否首次加载 (v5 推荐) isFetching, // 是否任何形式的获取中 (包括后台刷新) isError, // 是否出错 error, // 错误对象 refetch // 手动触发重新获取的函数 } = useQuery({ queryKey: ['todos'], // 必需:唯一标识此查询的键 (数组或字符串) queryFn: async () => { // 必需:执行数据获取的异步函数 const response = await axios.get('/api/todos'); return response.data; }, // 可选配置项: // staleTime: 5 * 60 * 1000, // 数据在 5 分钟内保持新鲜 // gcTime: 10 * 60 * 1000, // 无活跃观察者 10 分钟后清除缓存 // refetchOnWindowFocus: false, // 窗口聚焦时不自动刷新 // enabled: computed(() => !!userId.value), // 仅当 userId 存在时才执行 });
-
queryKey
: 极其重要 。它用于内部缓存和依赖跟踪。键的改变会触发新的查询。通常包含资源名称和依赖参数,如['todos']
,['todos', { status: 'done' }]
,['post', postId]
。 -
queryFn
: 必须返回一个 Promise。Vue Query 会处理 Promise 的解析和拒绝。 -
状态管理: Vue Query 自动管理
isLoading
/isPending
,isFetching
,isError
,status
等状态,你可以直接在模板中使用这些响应式 ref。 -
缓存与同步: 内置 Stale-While-Revalidate 策略、后台自动刷新、窗口聚焦刷新等功能(可通过
options
配置)。
-
-
变更 (Mutations):
useMutation
-
用途: 执行改变服务器数据的操作(POST, PUT, DELETE 等)。
-
基本用法:
typescriptimport { useMutation, useQueryClient } from '@tanstack/vue-query'; import axios from 'axios'; const queryClient = useQueryClient(); // 获取 QueryClient 实例 const { mutate, // 触发变更的函数 (不返回 Promise) mutateAsync, // 触发变更并返回 Promise 的函数 status, // 'idle', 'pending', 'error', 'success' isPending, // 是否正在执行变更 isError, error, data: mutationResult // 变更成功后的结果 } = useMutation({ mutationFn: async (newTodo) => { // 必需:执行变更的异步函数 const response = await axios.post('/api/todos', newTodo); return response.data; }, // 可选配置项: onSuccess: (data, variables, context) => { // 变更成功后的回调,极其常用! // 1. 使相关查询失效,让它们在下次访问时重新获取 queryClient.invalidateQueries({ queryKey: ['todos'] }); // 2. 或者,直接手动更新缓存 (更即时,但可能更复杂) // queryClient.setQueryData(['todos'], (oldData) => [...oldData, data]); console.log('Todo added!', data); }, onError: (error, variables, context) => { console.error('Failed to add todo:', error); // 可以在这里处理错误,例如显示通知 }, // onMutate: (variables) => { // // 乐观更新:在 mutationFn 执行前立即修改缓存 // // 需要返回 context 用于 onError 回滚 // }, // onSettled: (data, error, variables, context) => { // // 无论成功失败都执行 // } }); // 在方法或事件处理中调用: function addTodo() { mutate({ title: 'New Task', completed: false }); }
-
核心流程: 调用
mutate
或mutateAsync
传入需要提交的数据 (variables
) -> 执行mutationFn
-> 根据结果调用onSuccess
或onError
-> 调用onSettled
。 -
缓存交互:
onSuccess
中最常见的做法是使用queryClient.invalidateQueries
来智能地让依赖该数据的查询失效,从而触发自动更新。
-
-
查询客户端 (
QueryClient
)-
核心: 是 Vue Query 的"大脑",管理所有查询的缓存、状态和配置。
-
设置: 通常在你的 Vue 应用入口 (如
main.js
/main.ts
) 创建一个QueryClient
实例,并通过VueQueryPlugin
提供给整个应用。typescriptimport { createApp } from 'vue'; import App from './App.vue'; import { VueQueryPlugin, QueryClient } from '@tanstack/vue-query'; const app = createApp(App); const queryClient = new QueryClient({ defaultOptions: { // 可以设置全局默认选项 queries: { staleTime: 5 * 60 * 1000, // 全局默认 5 分钟新鲜期 }, }, }); app.use(VueQueryPlugin, { queryClient }); // 注册插件 app.mount('#app');
-
交互: 可以通过
useQueryClient()
hook 在组件中获取queryClient
实例,然后调用其方法手动与缓存交互,如invalidateQueries
,setQueryData
,refetchQueries
等。
-
-
缓存机制 (Stale-While-Revalidate)
- Vue Query 默认采用此策略:
- Stale Time (
staleTime
): 数据保持"新鲜"的时间。默认0
。 - GC Time (
gcTime
): 数据在无活跃观察者后,在缓存中保留多久才被垃圾回收。默认 5 分钟。
- Stale Time (
- 当数据被视为"过时"(超过
staleTime
)但未被回收时,Vue Query 会:- 立即返回缓存的过时数据 (UI 快速响应)。
- 在后台自动发起重新获取请求 (
isFetching
为true
)。 - 请求成功后更新缓存和 UI。
- Vue Query 默认采用此策略:
五、 核心优势与收益
- 代码简洁: 大幅减少与数据获取、缓存、同步相关的模板代码和逻辑。
- 提升用户体验: 缓存、后台刷新、Stale-While-Revalidate 使应用感觉更快、更流畅。乐观更新进一步提升交互感。
- 性能优化: 自动请求去重、智能缓存减少了不必要的网络负载。
- 开发效率: 开发者可以更专注于业务逻辑,而不是底层的状态管理细节。API 直观易用。
- 强大的 DevTools: 提供 Vue DevTools 集成或独立的 TanStack Query DevTools 组件,可以清晰地查看缓存状态、查询细节、手动触发操作等,极大方便调试。
- 健壮性: 内置重试机制,处理各种边界情况。
六、 何时使用 Vue Query?
- 任何需要与后端 API 或其他异步数据源交互的场景。
- 需要缓存服务器数据以提升性能或减少 API 调用的应用。
- 需要处理复杂的加载、错误、数据同步状态的界面。
- 实现分页、无限滚动等数据列表功能。
- 执行 CRUD 操作并需要更新相关视图。
- 希望实现乐观更新。
七、 Vue Query vs. Pinia/Vuex
- 它们不是替代关系,而是互补关系!
- Pinia/Vuex: 主要管理客户端状态(UI状态、全局设置、表单状态、用户会话的部分信息等)。
- Vue Query: 主要管理服务器状态(API数据获取、缓存、同步)。
- 最佳实践: 在复杂应用中,两者结合使用 。用 Vue Query 处理所有服务器交互,用 Pinia 管理纯客户端的全局状态。例如,用 Vue Query 获取用户数据,然后将用户的登录状态 (
isLoggedIn
) 和 ID 存入 Pinia store。
八、 总结
Vue Query (TanStack Query for Vue) 是一个革命性的库,它极大地简化了在 Vue 应用中管理服务器状态的复杂性。通过其强大的查询、变更、缓存和同步机制,可以显著提升开发效率、应用性能和最终的用户体验。对于任何需要处理异步数据的现代 Vue 应用,Vue Query 都是一个非常值得学习和使用的工具。