03-Vue Query 高级进阶：应对复杂业务场景的硬核套路
前言在前两篇文章中，我们完成了 Vue Query 的思维转变并掌握了基础实战。然而，在真实的中大型项目中，我们往往会遇到许多刁钻、复杂的业务需求。今天，我们将分享 4 个应对复杂场景的高级套路，帮助你彻底玩转 Vue Query。[hide]优雅的 Key 层次设计与全局缓存失效业务场景在需要登录鉴权的项目中，当用户点击“退出登录”时，我们必须立即清空所有与该用户相关的敏感接口缓存。否则，当下一个用户登录时，可能会短暂看到上一个用户的数据，造成严重的数据串线和安全隐患。如果一个一个去手动清理 user-detail、notice-list、billing-history，不仅代码极其繁琐，而且后续新增接口时极易遗漏。解决方案：多级 Key 继承与前缀匹配Vue Query 的缓存失效机制（invalidateQueries）是基于前缀匹配的。我们可以利用这一特性，设计一套优雅的 Key 层次结构。首先，定义一个基础键：/** 必须登录的查询基础 key */ export const REQUIRED_QUERY_KEY = ["required"];然后，让所有需要登录鉴权的接口 Key 都继承这个基础键：用户信息 Key：[...REQUIRED_QUERY_KEY, "user-detail"]公告列表 Key：[...REQUIRED_QUERY_KEY, "notice-list"]API 地址 Key：[...REQUIRED_QUERY_KEY, "api-address"]当用户登出时，我们只需要一行代码：import { queryClient } from "@/plugins/tanstack/vue-query"; import { REQUIRED_QUERY_KEY } from "./query-keys"; /** 将所有需要登录的查询标记为过期并清空 */ export function invalidateRequiredQueries() { return queryClient.invalidateQueries({ queryKey: REQUIRED_QUERY_KEY }); }原理解析：由于 Vue Query 会匹配所有以 ["required"] 开头的 Key，因此调用 invalidateRequiredQueries 会自动、一次性地让所有敏感接口的缓存全部失效并重新拉取。这极大地提升了系统的安全性和可维护性。分页与过滤：如何消除页面切换的闪烁感？业务场景在公告列表、账单日志等分页表格中，当用户点击“下一页”时，由于新一页的数据正在网络传输中，页面通常会瞬间变为空白，或者显示一个全屏的 Loading 遮罩。等数据加载完后，表格再重新渲染。这种频繁的“空白 -> 渲染”切换会产生强烈的闪烁感，用户体验非常粗糙。解决方案：placeholderData: keepPreviousDataVue Query 提供了 keepPreviousData 配置项，专门用来解决分页切换的闪烁问题。import { computed, toValue, type Ref } from "vue"; import { keepPreviousData, useQuery } from "@tanstack/vue-query"; import { getNoticeList, type GetNoticeListParams } from "@/api/notice"; export const NOTICE_LIST_KEY = ["notice-list"]; export function useGetNoticeList(params: Ref<GetNoticeListParams>) { return useQuery({ queryKey: computed(() => [...NOTICE_LIST_KEY, toValue(params)]), queryFn: () => getNoticeList(toValue(params)), // 核心配置：在请求新数据时，保持显示上一页的数据 placeholderData: keepPreviousData }); }原理解析：当用户切换到第 2 页时，Vue Query 会在后台发起第 2 页的请求。在第 2 页数据返回之前，页面上依然显示第 1 页的数据，表格不会变空，避免了布局闪烁。你可以配合 isFetching 状态，在表格上方显示一个轻量级的加载条（如进度条），提示用户正在加载。一旦第 2 页数据返回，Vue Query 会瞬间将表格内容替换为第 2 页数据，实现无缝平滑过渡。突破 Setup 限制：在非组件上下文中同步读取缓存业务场景在路由跳转前（如 Vue Router 的全局前置守卫 beforeEach 中），我们需要判断用户是否已经实名认证。如果未认证，则拦截并跳转到认证页面。路由守卫是一个普通的 TS 函数，不属于 Vue 组件，无法使用 useQuery 等 Composable 函数。我们该如何获取缓存的用户信息？解决方案：直接操作全局 queryClient 单例我们可以直接通过全局导出的 queryClient 实例，使用 getQueryData 同步读取内存中的缓存。// src/router/auth-guard.ts import { queryClient } from "@/plugins/tanstack/vue-query"; import { USER_DETAIL_KEY } from "@/queries/user/useUserDetail"; import type { UserDetailResult } from "@/api/user"; router.beforeEach((to, from, next) => { // 1. 同步读取已有的用户详情缓存（无网络开销，直接读内存） const userDetail = queryClient.getQueryData<UserDetailResult>(USER_DETAIL_KEY); // 2. 读取实名认证状态 const isRealNameVerified = userDetail?.user?.realAuth ?? false; if (to.meta.requiresAuth && !isRealNameVerified) { // 3. 未实名认证，拦截并跳转 next({ name: "RealNameAuth" }); } else { next(); } });通过 queryClient.getQueryData，我们可以在任何非 Setup 环境下，同步、无开销地读取内存中的缓存数据，完美解决了非组件环境下的状态共享问题。命令式异步获取：ensureQueryData 的妙用业务场景在某些弹窗或异步工作流中，我们需要获取某项配置数据（例如 API 物理地址）。我们希望：如果缓存里有，就直接拿来用；如果缓存里没有，就立刻发起网络请求，并等待请求完成后返回结果。解决方案：queryClient.ensureQueryDataVue Query 提供了 ensureQueryData 方法，它能完美地将“声明式缓存”与“命令式工作流”结合在了一起。// src/queries/api-keys/useApiAddress.ts import { queryClient } from "@/plugins/tanstack/vue-query"; import { getApiAddress } from "@/api/api-keys"; export const API_ADDRESS_KEY = ["api-address"]; const getApiAddressQueryOptions = () => ({ queryKey: API_ADDRESS_KEY, queryFn: () => getApiAddress(), staleTime: 30 * 60 * 1000 // 30分钟有效期 }); /** 命令式获取数据：如果缓存有效则直接返回，否则发起请求并等待 */ export function getApiAddressAsync() { return queryClient.ensureQueryData(getApiAddressQueryOptions()); }在弹窗组件或异步工作流中，你可以像调用普通异步函数一样使用它：import { getApiAddressAsync } from "@/queries/api-keys/useApiAddress"; const handleExport = async () => { // 1. 命令式获取 API 地址（有缓存读缓存，无缓存发请求并等待） const apiAddress = await getApiAddressAsync(); // 2. 执行后续的导出逻辑 await executeExport(apiAddress); };依赖查询：如何处理有先后顺序的串行请求？业务场景在实际业务中，我们经常遇到“请求 B 依赖请求 A 的结果”的场景。例如：我们需要先获取“当前用户的组织 ID（OrgId）”，然后再根据这个“组织 ID”去获取“该组织的权限列表”。如果使用传统的 onMounted，我们需要在第一个请求的 then 回调中去触发第二个请求，代码很容易陷入回调地狱。解决方案：enabled 动态控制在 Vue Query 中，我们可以通过将前一个请求的返回结果作为后一个请求的 enabled 条件，优雅地实现串行依赖查询。import { computed } from "vue"; import { useQuery } from "@tanstack/vue-query"; import { getUserProfile, getOrgPermissions } from "@/api/user"; export function useOrgPermissions() { // 1. 第一个查询：获取用户个人资料（包含组织 ID） const userQuery = useQuery({ queryKey: ["user-profile"], queryFn: getUserProfile }); // 2. 提取组织 ID const orgId = computed(() => userQuery.data.value?.orgId); // 3. 第二个查询：依赖第一个查询的结果 const permissionsQuery = useQuery({ queryKey: computed(() => ["org-permissions", orgId.value]), queryFn: () => getOrgPermissions(orgId.value!), // 核心配置：只有当 orgId 存在时，才启用第二个查询 enabled: computed(() => !!orgId.value) }); return { userQuery, permissionsQuery }; }原理解析：页面加载时，orgId 为 undefined，因此 permissionsQuery 的 enabled 为 false，处于 pending 状态，不会发送请求。当 userQuery 请求成功并返回数据后，计算属性 orgId 自动更新。permissionsQuery 监听到 enabled 变为 true，且 queryKey 自动更新为最新的 ["org-permissions", actualOrgId]，随即自动触发网络请求。整个过程完全是声明式的，没有任何嵌套的回调函数，代码极其优雅。总结通过这三篇文章，我们完成了 @tanstack/vue-query 的全方位探索：第一阶段：我们转变了思维，理解了“传输层”与“异步状态管理层”的解耦协同。第二阶段：我们快速入门，掌握了声明式查询、响应式 Key 联动以及轻量级缓存更新。第三阶段：我们深入底层，通过多级 Key 继承、分页防抖、非 Setup 同步读取、命令式异步获取以及依赖查询，优雅地解决了各种刁钻的实战难题。希望这套实战指南能帮助你在项目中写出更优雅、更健壮、体验更极致的代码！[/hide]
02-Vue Query 快速入门：从零构建你的第一个声明式查询
前言在上一篇文章中，我们探讨了为什么要引入 @tanstack/vue-query 以及它与 Axios 的协同关系。今天，我们将正式进入实战阶段，手把手教你如何在 Vue 3 项目中搭建 Vue Query 的骨架，掌握其最核心的 API 与配置，并深入探讨如何利用调试工具和轻量级缓存更新策略提升开发效率。[hide]基础配置：搭建 Vue Query 的骨架在 Vue 3 项目中集成 Vue Query 非常简单，主要分为两步：初始化 QueryClient 和注册插件。第一步：初始化 QueryClient我们在项目根目录的 src/plugins/tanstack/vue-query.ts 中创建并配置 QueryClient 实例。import { QueryClient, VueQueryPlugin } from "@tanstack/vue-query"; import type { App } from "vue"; // 实例化 QueryClient，并导出为全局单例 export const queryClient = new QueryClient({ defaultOptions: { queries: { // 1. 因为我们的 Axios 拦截器已经实现了请求重试，所以这里关闭 Vue Query 的自动重试 retry: false, // 2. 默认关闭窗口聚焦时自动刷新（按需在单个接口开启，省流量） refetchOnWindowFocus: false } } });架构小贴士：为什么要把 queryClient 导出为全局单例？因为在实际项目中，我们经常需要在非 Setup 上下文（如路由守卫、Pinia Store、Axios 拦截器或普通 JS/TS 工具函数）中操作缓存。通过导出单例，我们可以在任何地方直接 import { queryClient } 并调用其 API（如 queryClient.setQueryData）。第二步：注册 VueQueryPlugin 与调试工具配置在同一个文件中，我们定义一个注册函数。对于接口数据管理工具而言，可视化调试能力至关重要。如果没有方便的可视化操作，调试缓存状态、查看数据是否过期会变得极其麻烦。根据 Vue Query 官方 Devtools 文档，官方目前提供了三种主流的调试方式：官方 Devtools 控制组件：通过在项目中引入 @tanstack/vue-query-devtools 组件，在页面右下角渲染一个悬浮的调试面板。集成到 vite-plugin-vue-devtools：Vue Query 可以无缝集成到 Vue 官方的 Vite 插件中，在 Vite 开发者面板中直接查看 Query 状态。浏览器插件（Chrome/Firefox Extension）：安装官方提供的浏览器独立扩展程序。在实际开发中，由于控制组件会侵入项目代码，而 Vite 插件面板有时不够直观，使用浏览器独立插件是许多开发者最习惯、最清爽的选择。为了让浏览器插件能够识别到我们项目中的 queryClient 实例，我们需要在开发环境下将其挂载到全局：export const registerVueQuery = (app: App) => { // 配合浏览器插件 Devtools 使用，且只在开发环境暴露 if (import.meta.env.DEV) { window.__TANSTACK_QUERY_CLIENT__ = queryClient; } app.use(VueQueryPlugin, { queryClient }); };挂载后，打开 Chrome 的 Vue Query Devtools 插件，即可完美识别并可视化操作我们应用中的所有缓存数据。最后，在 main.ts 中引入并调用 registerVueQuery(app) 即可完成初始化。核心 API：useQuery 的基本用法useQuery 是 Vue Query 中最常用的 API，用于执行“读”操作（获取数据）。我们以一个标准的用户信息接口为例来讲解。import { useQuery } from "@tanstack/vue-query"; import { getUserDetail } from "@/api/user"; export const USER_DETAIL_KEY = ["user-detail"]; export function useUserDetail() { return useQuery({ // 1. QueryKey：缓存的唯一标识，必须是数组 queryKey: USER_DETAIL_KEY, // 2. QueryFn：一个返回 Promise 的异步请求函数 queryFn: getUserDetail, // 3. StaleTime：数据有效期（5分钟） staleTime: 5 * 60 * 1000 }); }核心参数解析queryKey：缓存的唯一标识。Vue Query 内部会以这个 Key 为键，将请求结果存入内存。只要 Key 不变，后续调用 useQuery 都会直接读取同一份缓存。queryFn：数据获取函数。它必须返回一个 Promise。这里我们直接传入了 Axios 封装的 getUserDetail。staleTime（失效时间）：在 staleTime 时间内，数据被认为是“新鲜的（Fresh）”。此时如果组件重新挂载，Vue Query 会直接从缓存中读取数据，不会发起网络请求。超过 staleTime 后，数据变为“过期的（Stale）”。此时如果组件重新挂载，Vue Query 会先返回缓存数据，同时在后台静默发起请求刷新数据。进阶控制：条件触发与响应式 Key在实际业务中，我们经常需要根据某些状态来控制请求的触发时机，或者根据参数的变化自动重新请求。条件触发（enabled）很多接口需要登录后才能请求。如果用户未登录，直接请求会导致 401 错误。我们可以通过 enabled 属性来控制：import { ref } from "vue"; export function useUserDetail(isLogin: Ref<boolean>) { return useQuery({ queryKey: USER_DETAIL_KEY, queryFn: getUserDetail, // 只有当 isLogin 为 true 时，才会自动发起请求 enabled: isLogin, staleTime: 5 * 60 * 1000 }); }响应式 Key（参数联动）当请求参数（如分页、搜索关键字）发生变化时，我们希望 Vue Query 自动重新发起请求。秘诀在于：将响应式变量绑定到 queryKey 中。以列表查询为例：import { computed, toValue, type Ref } from "vue"; import { getNoticeList, type GetNoticeListParams } from "@/api/notice"; export const NOTICE_LIST_KEY = ["notice-list"]; export function useGetNoticeList(params: Ref<GetNoticeListParams>) { return useQuery({ // 将响应式的 params 传入 queryKey 数组中 // 当 params 发生变化时，queryKey 也会变化，Vue Query 会自动触发重新请求 queryKey: computed(() => [...NOTICE_LIST_KEY, toValue(params)]), queryFn: () => getNoticeList(toValue(params)) }); }数据更新与缓存失效：Mutation 与命令式更新在处理“写”操作（增删改）时，如何优雅地更新本地缓存，是决定项目架构是否清爽的关键。Vue Query 提供了声明式的 useMutation，同时也支持通过 QueryClient 进行命令式的缓存操作。这两种模式各有其适用场景。官方推荐用法：useMutation 声明式封装Vue Query 官方推荐使用 useMutation 来处理数据修改。它将“发送修改请求”与“成功后使缓存失效”封装在一起，形成逻辑闭环。以下是一个完整的 useMutation 封装与组件内使用的示例，它与我们之前的 useUserDetail 缓存无缝关联：// src/queries/user/useUpdateNickname.ts import { useMutation, useQueryClient } from "@tanstack/vue-query"; import { updateNickname } from "@/api/user"; import { USER_DETAIL_KEY } from "./useUserDetail"; export function useUpdateNickname() { const queryClient = useQueryClient(); return useMutation({ // 1. 绑定修改数据的 API 请求函数 mutationFn: (newNickname: string) => updateNickname(newNickname), // 2. 修改成功后的回调 onSuccess: () => { // 3. 使之前的用户信息缓存失效，触发 useUserDetail 自动在后台静默刷新 queryClient.invalidateQueries({ queryKey: USER_DETAIL_KEY }); } }); }在 Vue 组件中的具体使用方式如下： 当前昵称：{{ userDetail?.nickname }} {{ isPending ? "保存中..." : "保存" }} 修改失败：{{ error.message }} 适用场景：强 UI 状态关联：当你的按钮需要根据请求状态展示“保存中...”，或者需要直接在模板中渲染修改失败的 error 提示时，useMutation 提供的响应式状态（isPending、error）能极大简化组件代码。灵活的“Axios 异步修改 + 命令式缓存更新”模式除了声明式的 useMutation，在实际开发中，我们也可以采用更符合传统直觉的“Axios 异步修改 + 命令式缓存更新”模式。我们不使用 useMutation，而是直接暴露普通的异步函数，并在其中操作全局的 queryClient 单例。模式 1：手动写入缓存（setQueryData）当用户登录成功后，后端会直接返回用户信息。此时我们不需要再发一次请求去获取详情，而是可以直接将数据写入缓存：import { queryClient } from "@/plugins/tanstack/vue-query"; import { USER_DETAIL_KEY } from "./useUserDetail"; /** 传入数据直接更新缓存 */ export function setUserDetail(newData: UserDetailResult) { queryClient.setQueryData(USER_DETAIL_KEY, newData); } // 在登录逻辑（如 useAuth.ts）中直接调用： // const loginResult = await login(credentials); // setUserDetail(loginResult.userDetail); // 瞬间写入缓存，全局共享！模式 2：使缓存失效（invalidateQueries）当用户修改了个人资料（如昵称）后，我们需要让本地的缓存失效，促使 Vue Query 自动去后端拉取最新数据：import { queryClient } from "@/plugins/tanstack/vue-query"; import { USER_DETAIL_KEY } from "./useUserDetail"; /** 将数据标记为过期，触发自动静默刷新 */ export function invalidateUserDetail() { return queryClient.invalidateQueries({ queryKey: USER_DETAIL_KEY }); } // 在普通的修改昵称函数中调用： async function handleUpdateNickname(newNickname: string) { await updateNickname(newNickname); // 1. 发送 Axios 请求修改数据 invalidateUserDetail(); // 2. 命令式使缓存失效，页面上的昵称会自动静默刷新！ }适用场景：极简的样样板代码：不需要在组件中引入 useMutation 并处理繁琐的解构，直接使用原生的 async/await 配合普通的 Axios 请求，符合大多数开发者的直觉。突破 Setup 限制：由于 queryClient 是全局单例，我们可以在 Pinia Store、路由守卫、甚至 Axios 拦截器等非 Setup 上下文中随时随地调用 setUserDetail 或 invalidateUserDetail，而不需要受 Vue 组合式函数必须在 setup 中执行的限制。总结今天我们掌握了 Vue Query 的基础实战套路：通过全局单例 queryClient 结合浏览器 Devtools 插件搭建高效的调试骨架。使用 useQuery 声明式获取数据，并利用 staleTime 控制缓存有效期。利用 enabled 实现条件触发，利用响应式 queryKey 实现参数联动。对比了官方 useMutation 声明式封装与更灵活的“Axios + 命令式缓存更新”模式，并明确了各自的适用场景。在下一篇文章中，我们将挑战一些更刁钻的业务场景，看看 Vue Query 的高级进阶用法（如多级 Key 继承、分页防抖、非 Setup 上下文调用等）是如何帮我们优雅破局的。[/hide]
前言在现代 Web 应用开发中，API 数据请求与状态同步是前端架构的核心环节。在 Vue 3 项目中，开发者通常习惯于使用 Axios 结合组件生命周期钩子（如 onMounted）来获取数据，并手动维护 loading、error 等响应式状态。然而，随着应用复杂度的提升，这种命令式的数据管理模式在性能、代码复用性以及用户体验上面临诸多挑战。本文将从架构设计的角度，探讨如何通过引入 @tanstack/vue-query 实现从“命令式数据获取”向“声明式异步状态管理”的范式转变，并阐述其与 Axios 的协同工作机制。传统前端请求的痛点在没有引入专门的异步状态管理工具前，一个典型的 Vue 3 数据请求组件通常采用如下模式： 加载中... 出错了：{{ error.message }} 用户：{{ data.name }} 这种命令式写法在简单场景下直观易懂，但在中大型项目中会暴露以下架构缺陷：状态冗余与样板代码：每个需要请求数据的组件都必须重复定义 loading、error、data 这一套响应式变量，并编写相似的 try-catch-finally 逻辑，导致代码冗余。重复请求与带宽浪费：若页面中多个独立挂载的子组件依赖同一份数据（例如当前登录用户信息），它们各自挂载时会触发多次相同的 API 请求，缺乏统一的请求合并机制。缓存缺失与页面闪烁：用户在不同路由间切换时，组件的销毁与重新挂载会频繁触发网络请求，导致页面出现“空白 -> 加载中 -> 渲染”的闪烁，无法提供即时响应的体验。数据过期与同步困难：客户端难以感知服务器端数据的实时变化。当用户在 A 页面修改了数据，B 页面缓存的数据无法自动或优雅地同步更新。核心概念：Axios 与 Vue Query 的本质区别要解决上述痛点，首先需要理清网络传输与状态管理在前端架构中的职责边界。Axios 的定位：传输层（Transport Layer）它是一个优秀的 HTTP 客户端，专注于“如何安全、正确地执行网络通信”。其职责包括：请求/响应拦截、Token 刷新、超时处理、请求重试、数据序列化等网络细节。它不关心数据在客户端的生命周期、共享范围以及缓存策略。Vue Query 的定位：异步状态管理层（Asynchronous State Management）它是一个强大的状态管理器，专注于“如何管理、同步和缓存客户端的服务器状态”。其职责包括：数据缓存生命周期管理、请求去重、后台静默刷新、条件触发、垃圾回收等。它不关心底层的网络请求是通过 Axios、Fetch 还是 WebSocket 实现的。协同工作模式在优秀的架构设计中，两者并非竞争关系，而是互补的。Axios 负责发送请求，Vue Query 负责管理请求结果。graph LR Component[Vue 组件] -->|useQuery| VueQuery[Vue Query 缓存层] VueQuery -->|未命中缓存/已过期| Axios[Axios 传输层] Axios -->|发起 HTTP 请求| Server[后端服务器] Server -->|返回数据| Axios Axios -->|交付数据| VueQuery VueQuery -->|响应式更新| Component引入 Vue Query 带来的核心收益声明式数据获取开发者不再需要手动维护 loading、error 状态。Vue Query 的 useQuery 会自动解构出这些状态，使组件逻辑更加聚焦于 UI 渲染：const { data, isLoading, isError, error } = useQuery({ queryKey: ["user-detail"], queryFn: getUserDetail });自动请求去重（Deduplication）当多个组件在同一时刻调用相同的 useQuery（拥有相同的 queryKey）时，Vue Query 会将它们合并。底层的网络请求只会发送一次，所有组件共享同一个响应结果。开箱即用的缓存与后台静默刷新（SWR 机制）Vue Query 采用了经典的 SWR (Stale-While-Revalidate) 缓存失效策略：当组件挂载时，如果缓存中已有数据，Vue Query 会立即返回缓存数据，让用户瞬间看到内容。与此同时，它会在后台静默发起请求，获取最新数据。当最新数据返回后，它会静默更新缓存并响应式地渲染页面，确保数据最终一致性。极简的全局状态共享无需通过 Pinia 编写繁琐的 action 和 state，直接通过相同的 QueryKey 即可在任意组件间共享数据，降低了状态管理的复杂度。项目实战：它们是如何在项目中优雅配合的？在实际项目中，我们通过将 Axios 实例与 Vue Query 的全局配置进行解耦，实现了职责分离。统一的初始化与职责分离在项目初始化阶段，我们配置全局的 QueryClient。由于底层的 Axios 实例已经处理了网络层面的请求重试和异常拦截（我自己封装的axios），我们在 Vue Query 层进行了相应的适配：import { QueryClient, VueQueryPlugin } from "@tanstack/vue-query"; export const queryClient = new QueryClient({ defaultOptions: { queries: { // 1. 底层 Axios 已经实现了请求重试机制，所以这里关闭自动重试，避免双重重试 retry: false, // 2. 默认关闭窗口聚焦时自动刷新，按需在单个接口开启，节省服务器流量 refetchOnWindowFocus: false } } });声明式查询封装示例在业务开发中，我们将 API 请求函数与 Vue Query 的缓存配置封装为统一的 Composable 函数。以下是一个标准的用户详情查询封装：import { useQuery } from "@tanstack/vue-query"; import { getUserDetail } from "@/api/user"; // 封装好的 Axios 请求函数 export const USER_DETAIL_KEY = ["user-detail"]; export function useUserDetail(isLogin: Ref<boolean>) { return useQuery({ queryKey: USER_DETAIL_KEY, queryFn: getUserDetail, // 只有在用户登录后，才允许发起请求（条件触发） enabled: isLogin, // 5分钟内，数据被认为是“新鲜”的，不需要重复请求 staleTime: 5 * 60 * 1000 }); }通过这种设计，底层的 Axios 专注于处理网络传输细节，而上层的 useUserDetail 则专注于处理缓存有效期、登录依赖等业务状态逻辑，实现了清晰的关注点分离。总结从“命令式 Axios 请求”到“声明式 Vue Query 缓存管理”，改变的不仅是几行代码，更是前端架构思维的转变。Axios 解决了“如何通信”的问题。Vue Query 解决了“如何同步状态”的问题。在下一篇文章中，我们将正式进入实战，手把手教你如何在项目中从零开始使用 Vue Query，并掌握其最核心的 API 与配置。