前置假设:
- 你已经安装了
@tanstack/vue-query
。 - 你已经在项目的入口文件(如
main.ts
)中设置好了QueryClient
和VueQueryPlugin
(如之前介绍中所示)。 - 你有一个发送 HTTP 请求的方式(这里以
axios
为例,但fetch
或其他库同样适用)。假设axios
已配置好,并在需要的地方导入。
示例 1:获取并展示列表数据 (例如:待办事项列表)
这是最基础的用法。
<script setup lang="ts">
import { useQuery } from '@tanstack/vue-query';
import axios from 'axios'; // 假设 axios 已配置
// 定义获取待办事项列表的函数
const fetchTodos = async () => {
console.log('发起请求获取 Todos...'); // 方便观察请求时机
const response = await axios.get('/api/todos');
return response.data; // Vue Query 需要 Promise 返回实际数据
};
// 使用 useQuery Hook
const {
data: todos, // 响应式的 ref,持有获取到的数据 (或 undefined)
status, // 查询状态: 'pending', 'error', 'success'
isLoading, // 响应式的 ref: 初始加载时为 true (v5 前常用)
isPending, // 响应式的 ref: 初始加载时为 true (v5 推荐)
isFetching, // 响应式的 ref: 任何获取过程中都为 true (包括后台刷新)
isError, // 响应式的 ref: 获取失败时为 true
error, // 响应式的 ref: 持有错误对象
refetch // 手动触发重新获取的函数
} = useQuery({
queryKey: ['todos'], // 必需:此查询的唯一标识键 (数组或字符串)
queryFn: fetchTodos, // 必需:用于获取数据的异步函数
// 可选配置:如果数据不经常变动,可以增加 staleTime
// staleTime: 5 * 60 * 1000, // 数据在 5 分钟内视为“新鲜”,不会因窗口聚焦等自动刷新
});
</script>
<template>
<div>
<h2>待办事项列表</h2>
<!-- 加载状态 -->
<div v-if="isPending">正在加载待办事项...</div>
<!-- 错误状态 -->
<div v-else-if="isError">发生错误: {{ error?.message }}</div>
<!-- 成功状态 -->
<ul v-else-if="todos">
<li v-for="todo in todos" :key="todo.id">
{{ todo.title }} - {{ todo.completed ? '已完成' : '待办' }}
</li>
</ul>
<!-- 无数据 -->
<div v-else>暂无待办事项。</div>
<!-- 后台刷新指示器 -->
<div v-if="isFetching && !isPending" class="fetching-indicator">
正在后台更新...
</div>
<!-- 手动刷新按钮 -->
<button @click="refetch()" :disabled="isFetching">
{{ isFetching ? '刷新中...' : '手动刷新' }}
</button>
</div>
</template>
<style scoped>
.fetching-indicator {
position: fixed;
bottom: 1rem;
right: 1rem;
background-color: lightblue;
padding: 0.5rem;
border-radius: 4px;
font-size: 0.8em;
}
</style>
说明:
queryKey: ['todos']
: 唯一标识了这个列表数据。如果其他地方也用这个 key 调用useQuery
,它们会共享缓存和状态。queryFn: fetchTodos
: 告诉 Vue Query 如何获取数据。- Vue Query 自动管理了
isPending
,isError
,data
等响应式状态,可以直接在模板中使用。 isFetching
表示任何获取过程,包括 Stale-While-Revalidate 或窗口聚焦触发的后台刷新。
示例 2:根据路由参数获取项目详情
这很常见,比如 /todos/1
显示 ID 为 1 的待办事项。
<script setup lang="ts">
import { useQuery } from '@tanstack/vue-query';
import { useRoute } from 'vue-router';
import { computed, watch } from 'vue';
import axios from 'axios';
const route = useRoute();
// 使用 computed 来响应式地获取路由参数 ID
const todoId = computed(() => route.params.id as string | undefined);
// 获取函数现在需要接收 ID
const fetchTodoById = async (id: string) => {
// 注意:最好处理 id 不存在或无效的情况
if (!id || Number.isNaN(parseInt(id, 10))) {
// 可以返回 null, undefined, 或抛出错误,取决于你希望如何处理
console.warn('无效的 Todo ID:', id);
return null;
// 或者 throw new Error('无效的 ID');
}
console.log(`发起请求获取 Todo ID: ${id}...`);
const response = await axios.get(`/api/todos/${id}`);
return response.data;
};
const {
data: todo,
isLoading, // 还是用 isLoading 吧,更清晰区分首次加载
isError,
error,
status, // 可以直接用 status 判断更细致的状态
// isFetching, // 如果需要后台刷新指示器也可以用
} = useQuery({
// Query Key 现在包含动态 ID。
// 当 todoId.value 变化时,Vue Query 会自动识别为不同的查询并获取数据!
queryKey: ['todo', todoId],
// queryFn 会接收一个包含 queryKey 的上下文对象,但直接用闭包访问 todoId 更方便
queryFn: () => fetchTodoById(todoId.value!), // 传递当前的 ID
// 只有当 ID 存在时才自动执行查询
enabled: computed(() => !!todoId.value && !Number.isNaN(parseInt(todoId.value, 10))),
});
// (可选) 监听 status 或 error 变化来执行副作用,如提示
watch(status, (newStatus) => {
console.log('Todo query status changed:', newStatus);
});
</script>
<template>
<div>
<h2>待办事项详情</h2>
<div v-if="isLoading">正在加载详情...</div>
<div v-else-if="isError">错误: {{ error?.message }}</div>
<div v-else-if="todo">
<h3>{{ todo.title }}</h3>
<p>状态: {{ todo.completed ? '已完成' : '待办' }}</p>
<p>ID: {{ todo.id }}</p>
</div>
<!-- 处理 fetchTodoById 返回 null 或 enabled 为 false 的情况 -->
<div v-else-if="!todoId">请提供有效的待办事项 ID。</div>
<div v-else>未找到该待办事项。</div>
</div>
</template>
说明:
queryKey: ['todo', todoId]
: 关键在于将响应式的todoId
(一个computed
ref) 放入queryKey
。当路由变化导致todoId.value
更新时,Vue Query 会自动检测到queryKey
的变化,并为新的 ID 获取数据(或使用该 ID 的缓存)。enabled: computed(() => !!todoId.value && ...)
: 这是一个非常有用的选项,它确保只有当todoId
有效时,查询才会自动执行,避免了在 ID 无效时发起不必要的 API 调用。
示例 3:添加项目 (Mutation + 缓存失效)
执行一个会改变数据的操作(如 POST 请求),然后更新列表视图。
<script setup lang="ts">
import { ref } from 'vue';
import { useMutation, useQueryClient } from '@tanstack/vue-query';
import axios from 'axios';
const queryClient = useQueryClient(); // 获取 QueryClient 实例,用于与缓存交互
const newTodoTitle = ref('');
// 定义执行变更的异步函数 (添加新的 todo)
const addTodoMutationFn = async (title: string) => {
console.log('发起请求添加 Todo:', title);
const response = await axios.post('/api/todos', { title, completed: false });
return response.data;
};
// 使用 useMutation Hook
const {
mutate: addTodo, // 调用此函数来触发变更操作 (不返回 Promise)
// mutateAsync: addTodoAsync, // 如果需要 Promise,用这个
status: addStatus, // 变更的状态: 'idle', 'pending', 'error', 'success'
isPending: isAddingTodo, // 是否正在执行变更
isError: isAddError,
error: addError,
data: addedTodoData, // 变更成功后 mutationFn 返回的数据
} = useMutation({
mutationFn: addTodoMutationFn,
// --- 关键:成功回调 ---
onSuccess: (newData, variables, context) => {
// 变更成功!最常用的策略:使 'todos' 查询失效。
// 这会告诉 Vue Query 与 ['todos'] 键关联的数据现在“不新鲜”了。
// 所有活跃的 useQuery(['todos']) 实例会自动在后台重新获取数据。
console.log('待办事项添加成功:', newData, ',原始变量:', variables);
// 使列表查询失效
queryClient.invalidateQueries({ queryKey: ['todos'] });
// --- 另一种选择:手动更新缓存 (更即时,但可能更复杂) ---
// 如果你想避免重新请求列表,可以手动更新缓存:
// queryClient.setQueryData(['todos'], (oldData: any[] | undefined) => {
// // 确保 oldData 是数组
// const currentData = Array.isArray(oldData) ? oldData : [];
// return [...currentData, newData]; // 返回更新后的数组
// });
newTodoTitle.value = ''; // 清空输入框
},
// --- 错误处理 ---
onError: (error, variables, context) => {
console.error('添加待办事项失败:', error, ',提交的数据:', variables);
// 可以在这里显示错误提示给用户
alert(`添加失败: ${error.message}`);
},
// onSettled: (data, error, variables, context) => {
// // 无论成功或失败都会执行
// console.log('添加操作完成');
// }
});
// 提交表单的处理函数
const submitNewTodo = () => {
const title = newTodoTitle.value.trim();
if (title) {
addTodo(title); // 调用 mutate 函数,传入需要的数据
}
};
</script>
<template>
<div>
<h3>添加新的待办事项</h3>
<form @submit.prevent="submitNewTodo">
<input type="text" v-model="newTodoTitle" placeholder="输入待办事项标题" :disabled="isAddingTodo" />
<button type="submit" :disabled="isAddingTodo">
{{ isAddingTodo ? '添加中...' : '添加' }}
</button>
</form>
<div v-if="isAddError" style="color: red;">
添加错误: {{ addError?.message }}
</div>
<!-- 列表组件 (如示例 1) 会因为 ['todos'] 查询失效并重新获取而自动更新 -->
</div>
</template>
说明:
useMutation
: 用于执行会改变服务器数据的操作。mutationFn
: 执行实际的 POST/PUT/DELETE 请求。mutate
: 调用这个函数来启动变更过程,并传入需要提交的数据。onSuccess
: 这是核心。变更成功后:queryClient.invalidateQueries({ queryKey: ['todos'] })
: 这是最常用也通常推荐的方式。它告诉 Vue Query['todos']
这个 key 的数据可能过期了。任何正在使用这个 key 的useQuery
都会自动安排一次后台刷新,从而让列表 UI 更新。简单、可靠。- 注释掉的
setQueryData
展示了手动更新缓存的方式,可以提供更即时的 UI 反馈(无需等待刷新),但需要你精确地维护缓存数据结构。
补充说明:
mutate
(即我们重命名后的addTodo
) 是一个由useMutation
提供的、用来启动整个变更流程的【触发器函数】。- 当你调用
mutate(variables)
时,它会在内部安排并调用你提供的addTodoMutationFn(variables)
这个异步函数。 - 但是,
mutate
函数本身的设计是**“发射后不管”(fire-and-forget)** 的风格,它不会等待addTodoMutationFn
这个异步操作完成,因此不返回 Promise。它只是启动流程,然后立即返回undefined
。
所以,可以这样理解:
mutate
启动了包含addTodoMutationFn
执行在内的整个变更工作流(包括调用onMutate
,onSuccess
,onError
,onSettled
等)。- 它间接调用了
addTodoMutationFn
。 - 但它不直接等同于
addTodoMutationFn
,并且刻意设计成不返回 Promise,以便调用它的地方(通常是 UI 事件处理)不会被异步操作阻塞。
与之对比的是 mutateAsync
:
mutateAsync
(即我们例子中注释掉的addTodoAsync
) 也是由useMutation
提供的触发器函数。- 它做的事情和
mutate
基本一样,也会启动整个变更流程并调用addTodoMutationFn
。 - 关键区别在于:
mutateAsync
会返回一个 Promise。这个 Promise 会在整个变更流程(包括mutationFn
和相关的回调)完成后解析 (resolve)(如果成功)或拒绝 (reject)(如果失败)。 - 使用场景: 当你需要在触发变更操作后,基于其最终成功或失败的结果来执行一些后续逻辑时(例如,导航到不同页面、显示特定成功/失败消息等),使用
mutateAsync
并配合await
或.then/.catch
会更方便。
简单总结:
mutate
(addTodo
): 像按下一个按钮,启动后台任务,按钮本身立即弹起(不等待任务完成)。用于简单触发,不关心后续结果的场景。mutateAsync
(addTodoAsync
): 像按下一个按钮,启动后台任务,并且按钮会一直“按下”直到任务完成,然后告诉你结果(成功或失败)。用于需要根据任务最终结果执行后续操作的场景。
可以理解为“mutate 包装了下 addTodoMutationFn,但不返回 promise” 很好地概括了这个核心行为!
示例 4:删除项目 (Mutation + 乐观更新)
乐观更新通过在服务器确认前就更新 UI,来提供更流畅的用户体验。
<script setup lang="ts">
import { useMutation, useQueryClient } from '@tanstack/vue-query';
import axios from 'axios';
// 假设这是 TodoItem 组件的一部分,接收一个 todo 对象作为 prop
interface Todo {
id: number;
title: string;
completed: boolean;
}
const props = defineProps<{ todo: Todo }>();
const queryClient = useQueryClient();
// 定义删除操作的函数
const deleteTodoMutationFn = async (id: number) => {
console.log(`发起请求删除 Todo ID: ${id}...`);
// DELETE 请求通常不返回内容,或者只返回状态码
await axios.delete(`/api/todos/${id}`);
};
// 使用 useMutation,配置乐观更新
const { mutate: deleteTodo, isPending: isDeleting } = useMutation({
mutationFn: deleteTodoMutationFn,
// --- 乐观更新逻辑 ---
onMutate: async (idToDelete) => {
console.log(`乐观更新:尝试删除 ID: ${idToDelete}`);
// 1. 取消可能正在进行的针对 'todos' 列表的重新获取请求
// 这可以防止它们覆盖我们的乐观更新。
await queryClient.cancelQueries({ queryKey: ['todos'] });
// 2. 获取当前的 'todos' 列表缓存快照
const previousTodos = queryClient.getQueryData<Todo[]>(['todos']);
// 3. 乐观地从缓存中移除这一项
if (previousTodos) {
queryClient.setQueryData<Todo[]>(
['todos'], // 要更新的缓存 key
// 更新函数:接收旧数据,返回新数据
(old) => old?.filter(todo => todo.id !== idToDelete) ?? []
);
console.log('乐观更新:UI 上的列表已更新');
} else {
console.log('乐观更新:未找到缓存数据,无法进行乐观更新');
}
// 4. 返回一个包含快照的上下文对象,以便在出错时回滚
return { previousTodos };
},
// --- 错误处理:回滚 ---
onError: (err, idToDelete, context) => {
console.error("删除失败,需要回滚:", err);
// 如果 onMutate 中成功获取了快照,则用它来恢复缓存
if (context?.previousTodos) {
console.log('回滚 UI...');
queryClient.setQueryData<Todo[]>(['todos'], context.previousTodos);
}
// 向用户显示错误信息
alert(`删除失败: ${err.message}`);
},
// --- 最终处理:确保一致性 ---
onSettled: (data, error, idToDelete, context) => {
// 无论删除成功还是失败,最终都要重新从服务器获取一次 'todos' 列表
// 来确保客户端状态与服务器状态最终一致。
console.log(`删除操作完成 (成功或失败),重新验证 'todos' 列表`);
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
// 处理删除按钮点击
const handleDelete = () => {
// 可以加一个确认框
if (confirm(`确定要删除 "${props.todo.title}" 吗?`)) {
deleteTodo(props.todo.id); // 调用 mutate 触发删除
}
};
</script>
<template>
<li>
{{ todo.title }}
<button @click="handleDelete" :disabled="isDeleting" style="margin-left: 10px;">
{{ isDeleting ? '删除中...' : '删除' }}
</button>
</li>
</template>
说明:
- 乐观更新 (Optimistic Update): 核心在于
onMutate
。UI 会在用户点击删除后立即移除该项,感觉非常流畅。 onMutate
:cancelQueries
: 防止后台刷新覆盖乐观更新。getQueryData
: 获取当前缓存。setQueryData
: 立即修改缓存,移除对应项。- 返回
context
: 保存原始数据用于回滚。
onError
: 如果服务器删除失败,使用context.previousTodos
通过setQueryData
将缓存恢复到原始状态。onSettled
: 极其重要。无论成功或失败,最后都要调用invalidateQueries({ queryKey: ['todos'] })
。这确保了客户端最终会与服务器的真实状态同步,修正了乐观更新可能带来的(暂时性)不一致。
这些示例覆盖了 Vue Query 最常用的一些场景。关键在于理解 queryKey
的作用、useQuery
和 useMutation
的基本用法,以及如何利用 QueryClient
的方法(尤其是 invalidateQueries
和 setQueryData
)来管理缓存和状态更新。根据你的具体需求调整这些模式即可。
问题解答
1. useQuery
的使用位置
useQuery
必须在组件的 setup
函数中调用,或者在能够访问 Vue 的依赖注入上下文的地方(例如自定义的 Composition API 函数中,这个函数本身也是在 setup
内被调用的)。
例如,在 Vue 3 的 <script setup>
中:
<script setup>
import { useQuery } from '@tanstack/vue-query';
// 在这里使用 useQuery 是正确的
const { data, isLoading, error } = useQuery({
queryKey: ['myData'],
queryFn: fetchDataFunction,
});
async function fetchDataFunction() {
// 你的异步数据请求逻辑
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
}
</script>
2. 避免重复请求和实现数据共享 (低频更新)
VueQuery 的核心优势之一就是它的缓存机制。要实现你的需求(低频更新接口,不同页面共享数据,避免不必要的重复请求),关键在于正确配置 useQuery
的选项,特别是 queryKey
和 staleTime
。
-
queryKey
: 这是一个数组,用作缓存的唯一标识符。在所有需要共享这份数据的组件中,务必使用完全相同的queryKey
。例如['sharedApiData']
。 -
staleTime
: 这个选项指定了数据在被视为“过时” (stale) 之前可以保持“新鲜” (fresh) 的时长(单位:毫秒)。- 在数据被视为新鲜期间,即使组件重新挂载或
useQuery
实例重新创建 (例如切换页面),VueQuery 也会直接从缓存中提供数据,而不会发起新的网络请求。 - 对于更新频率很低的接口,你可以将
staleTime
设置为一个较大的值,比如几分钟、几小时,甚至Infinity
(如果数据几乎永不改变,只在手动触发时更新)。
- 在数据被视为新鲜期间,即使组件重新挂载或
-
cacheTime
: 这个选项定义了数据在从缓存中被垃圾回收之前可以保留的时长(单位:毫秒)。即使数据已经 "stale",只要它还在缓存中 (未超过cacheTime
) 并且没有活动的useQuery
实例监听它,它就会被保留。默认是 5 分钟。如果你的staleTime
设置得非常长 (比如Infinity
),那么cacheTime
也应该相应设置得足够长,否则 "fresh" 的数据可能会因为缓存过期而被清除。通常,cacheTime
应大于等于staleTime
。VueQuery 内部如果staleTime
比cacheTime
大,它会表现得像cacheTime
和staleTime
一样长。为了简单起见,如果你设置了很长的staleTime
,也设置一个同样长的cacheTime
。 -
其他相关选项:
refetchOnMount
: 默认为true
。如果数据是 stale 的,组件挂载时会重新获取。如果staleTime
设置得足够长,数据会长时间保持 fresh,即使这个选项是true
,也不会在挂载时重新获取。如果希望更严格地控制,可以设为false
,这样只有在数据不存在或明确需要时才获取。refetchOnWindowFocus
: 默认为true
。窗口重新获得焦点时会重新获取 stale 数据。对于低频更新的数据,可以设为false
。refetchOnReconnect
: 默认为true
。网络重连后会重新获取 stale 数据。同样,可以设为false
。
解决方案示例:
假设你有一个获取用户配置的接口,这个配置很少变动。
在你的 Vue 组件中 (PageA.vue
, PageB.vue
等):
<script setup>
import { useQuery } from '@tanstack/vue-query';
const QUERY_KEY_USER_CONFIG = ['userConfig'];
// 模拟 API 请求函数
async function fetchUserConfig() {
console.log('Fetching user config from API...'); // 观察这个日志的打印次数
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 1000));
return {
id: 1,
name: 'Test User',
preferences: {
theme: 'dark',
notifications: true,
},
lastUpdated: new Date().toLocaleTimeString(),
};
}
const { data: userConfig, isLoading, isError, error } = useQuery({
queryKey: QUERY_KEY_USER_CONFIG, // 关键:所有页面使用相同的 queryKey
queryFn: fetchUserConfig,
staleTime: 1000 * 60 * 30, // 数据在30分钟内是新鲜的
// cacheTime: 1000 * 60 * 35, // 缓存35分钟 (确保 >= staleTime)
// VueQuery 默认 cacheTime 是 5 分钟。如果 staleTime 比 cacheTime 长,
// query 会在 staleTime 之后才 inactive,然后 cacheTime 开始计时。
// 如果你希望在数据变为 stale 后,还能在缓存中保留更久,可以显式设置更长的 cacheTime。
// 对于 staleTime: Infinity 的情况,cacheTime 也应该设为 Infinity 或足够长。
refetchOnWindowFocus: false, // 关闭窗口聚焦时重新获取
refetchOnMount: false, // 关闭挂载时重新获取 (如果数据已存在且新鲜,即使true也不会获取;设为false更保险)
refetchOnReconnect: false, // 关闭网络重连时重新获取
});
</script>
<template>
<div>
<div v-if="isLoading">Loading user config...</div>
<div v-else-if="isError">Error fetching config: {{ error?.message }}</div>
<div v-else-if="userConfig">
<h2>User Configuration ({{ userConfig.lastUpdated }})</h2>
<pre>{{ userConfig }}</pre>
</div>
</div>
</template>
说明:
- 一致的
queryKey
:['userConfig']
在所有使用此数据的页面中保持一致。 staleTime: 1000 * 60 * 30
: 数据获取后,30分钟内被认为是新鲜的。当你从一个页面切换到另一个使用相同queryKey
的页面时,只要还在30分钟内,fetchUserConfig
就不会被再次调用,useQuery
会直接返回缓存中的数据。refetchOnWindowFocus: false
,refetchOnMount: false
,refetchOnReconnect: false
: 这些设置进一步减少了自动重新获取数据的机会,更符合“低频更新”的需求。
全局配置 (推荐):
你还可以在创建 QueryClient
时设置默认的查询选项,这样就不需要在每个 useQuery
中重复配置。
// main.js 或 main.ts
import { 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: 1000 * 60 * 5, // 默认staleTime为5分钟
refetchOnWindowFocus: false,
// ... 其他全局默认配置
},
},
});
app.use(VueQueryPlugin, { queryClient }); // 或者 app.use(VueQueryPlugin, { queryClientConfig: { defaultOptions: { ... } } })
app.mount('#app');
然后,在组件中 useQuery
时,除非你想覆盖全局配置,否则它会使用这些默认值。
对比 Pinia/自定义 Hook 的“有无数据”逻辑:
VueQuery 的 staleTime
和缓存机制,结合其自动管理数据状态(isLoading
, isFetching
, isSuccess
, isError
, data
等),本身就优雅地处理了“判断有无数据,无数据接口请求”的逻辑。
- 当
useQuery
首次为一个queryKey
执行时,或者当缓存数据已不存在或已 stale 且需要 refetch 时,它会调用queryFn
。 - 如果缓存中有对应
queryKey
的 fresh 数据,它会直接使用缓存数据,queryFn
不会执行。
这种方式比手动在 Pinia store 或自定义 hook 中检查数据是否存在然后决定是否调用 API 更加声明式和强大,因为它还内置了后台更新、重试、依赖查询等高级功能。
通过上述配置,你应该能够在不同页面间高效共享低频更新的接口数据,避免不必要的请求。切换页面时,只要数据在 staleTime
内,console.log('Fetching user config from API...');
应该不会频繁打印。
3. refetchOnReconnect
参数详解
当 refetchOnReconnect
设置为 true
(默认值)时,如果网络断开后重新连接,Vue Query 会尝试重新获取那些当前处于活动状态 (active) 并且数据状态为 "stale" (过时) 的查询。
这里有几个关键点需要理解:
-
活动状态 (Active Queries):
- 一个查询被认为是“活动的”,如果它至少有一个正在被使用的
useQuery
实例(即,相关的组件当前是挂载的,并且正在监听这个查询)。 - 如果一个查询没有活动的实例(比如所有使用该
queryKey
的组件都已卸载),那么即使refetchOnReconnect: true
,它在网络重连时通常也不会被自动重新获取,直到它再次变为活动状态。
- 一个查询被认为是“活动的”,如果它至少有一个正在被使用的
-
数据状态为 "Stale" (过时):
- 只有当查询的数据被认为是 "stale" 时,
refetchOnReconnect: true
才会触发重新获取。 - 数据是否 "stale" 取决于你的
staleTime
配置。如果staleTime
设置得非常长(例如Infinity
),那么数据会长时间保持 "fresh"(新鲜)状态。在这种情况下,即使网络重连,并且refetchOnReconnect: true
,由于数据仍然是 "fresh",Vue Query 也不会发起新的请求。
- 只有当查询的数据被认为是 "stale" 时,
-
"所有地方"的含义:
- 它不是指你代码库中定义过的所有
useQuery
,而是指那些在网络重连那一刻,同时满足“活动状态”和“数据已过时”这两个条件的查询。 - 所以,如果你的应用中有多个组件使用了不同的
queryKey
,并且它们在网络重连时都是活动的,并且它们各自的数据都已过时,那么它们都会尝试重新获取。
- 它不是指你代码库中定义过的所有
总结一下:
- 会重新请求:那些在网络重连时,其关联组件是挂载的(查询是活动的),并且它们的数据已经超过了
staleTime
(数据是 stale 的)。 - 不会重新请求:
- 那些数据仍然是 "fresh" 的查询(未超过
staleTime
)。 - 那些没有活动实例的查询(例如,相关组件已卸载)。
- 那些数据仍然是 "fresh" 的查询(未超过
如何控制这种行为?
-
全局设置:你可以在创建
QueryClient
时,为所有查询设置默认的refetchOnReconnect
行为:// main.js 或 main.ts import { QueryClient } from '@tanstack/vue-query'; const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnReconnect: false, // 全局禁用网络重连时自动重新获取 // 或者设置为 true (默认), 或者一个函数 (()=>{...}) 来自定义逻辑 }, }, });
-
单个查询设置:你也可以在每个
useQuery
中单独覆盖这个设置:<script setup> import { useQuery } from '@tanstack/vue-query'; useQuery({ queryKey: ['myData'], queryFn: fetchData, refetchOnReconnect: false, // 对这个特定查询禁用 }); </script>
对于你的场景,如果接口更新频率很低,并且你不希望网络波动或短暂断开后立即触发大量请求,那么将 refetchOnReconnect
设置为 false
(无论是全局还是针对特定查询) 可能是一个更合适的选择。这样,数据将仅根据 staleTime
、refetchOnMount
、refetchOnWindowFocus
等其他配置或手动操作来更新。