译零 TransZero · TanStack Query 中文文档
主站

响应性

更新于 2026-11-02 Vue 查看英文原文 ↗

Vue 使用 信号范式(signals paradigm) 来处理和追踪响应性。该系统的一个关键特性是:响应式系统仅在明确监听的响应式属性发生变化时才触发更新。由此带来的一个后果是:你还需要确保当查询所依赖的值发生变化时,查询本身也得到相应更新。

保持查询的响应性

当你为某个查询编写组合式函数(composable)时,第一种写法可能是这样:

TypeScript
1export function useUserProjects(userId: string) {2  return useQuery({3    queryKey: ['userProjects', userId],4    queryFn: () => api.fetchUserProjects(userId),5  })6}

我们可能会像这样使用该组合式函数:

TypeScript
1// Reactive user ID ref.2const userId = ref('1')3// Fetches the user 1's projects.4const { data: projects } = useUserProjects(userId.value)5 6const onChangeUser = (newUserId: string) => {7  // Edits the userId, but the query will not re-fetch.8  userId.value = newUserId9}

这段代码无法按预期工作。原因在于我们直接从 userId ref 中解构出了其值。Vue Query 并未追踪 userId ref,因此无法感知该值何时发生变化。

幸运的是,这个问题的修复非常简单:只需将该值纳入查询键(query key)中使其可被追踪即可。我们可以直接在组合式函数中接收 ref,并将其放入查询键中:

TypeScript
1export function useUserProjects(userId: Ref<string>) {2  return useQuery({3    queryKey: ['userProjects', userId],4    queryFn: () => api.fetchUserProjects(userId.value),5  })6}

现在,当 userId 发生变化时,查询便会重新拉取。

TypeScript
1const onChangeUser = (newUserId: string) => {2  // Query refetches data with new user ID!3  userId.value = newUserId4}

在 Vue Query 中,查询键内任何响应式属性都会被自动追踪变化。这使得 Vue Query 能在给定请求的参数发生变化时自动重新拉取数据。

处理非响应式查询

尽管可能性较小,但有时传入非响应式变量确实是刻意为之。例如,某些实体只需拉取一次、无需追踪变化;又或者我们会在修改(mutation)后手动使查询失效。若在此类场景下仍使用上面定义的自定义组合式函数,则用法会略显别扭:

TypeScript
1const { data: projects } = useUserProjects(ref('1'))

我们必须创建一个中间 ref,仅仅为了满足类型兼容性要求。我们可以做得更好:更新组合式函数,使其同时支持普通值(plain values)和响应式值(reactive values):

TypeScript
1export function useUserProjects(userId: MaybeRef<string>) {2  return useQuery({3    queryKey: ['userProjects', userId],4    queryFn: () => api.fetchUserProjects(toValue(userId)),5  })6}

现在,我们既可用普通值,也可用 ref 来调用该组合式函数:

TypeScript
1// Fetches the user 1's projects, userId is not expected to change.2const { data: projects } = useUserProjects('1')3 4// Fetches the user 1's projects, queries will react to changes on userId.5const userId = ref('1')6 7// Make some changes to userId...8 9// Query re-fetches based on any changes to userId.10const { data: projects } = useUserProjects(userId)

在查询中使用派生状态

从另一响应式状态源派生出新的响应式状态是很常见的。典型场景之一是处理组件 props。假设我们的 userId 是作为 prop 传入组件的:

HTML
1<script setup lang="ts">2const props = defineProps<{3  userId: string4}>()5</script>

你可能倾向于像这样直接在查询中使用该 prop:

TypeScript
1// Won't react to changes in props.userId.2const { data: projects } = useUserProjects(props.userId)

然而,与第一个示例类似,这种写法并不具备响应性。对 reactive 变量进行属性访问会导致响应性丢失。我们可通过 computed 将该派生状态转为响应式来解决此问题:

TypeScript
1const userId = computed(() => props.userId)2 3// Reacts to changes in props.userId.4const { data: projects } = useUserProjects(userId)

该方案能按预期工作,但并非总是最优选择。除引入中间变量外,我们还创建了一个实际上并无必要的记忆化(memoized)值。对于简单的属性访问这类琐碎场景,computed 作为一种优化并无实际收益。此时更合适的方案是使用 响应式 getter(reactive getters)。响应式 getter 本质上是返回基于某些响应式状态之值的函数,其工作方式类似于 computed;但与 computed 不同,响应式 getter 不会对返回值做记忆化,因此更适合用于简单的属性访问。

让我们再次重构组合式函数,这次让它同时接受 ref、普通值或响应式 getter:

TypeScript
1export function useUserProjects(userId: MaybeRefOrGetter<string>) {2  ...3}

接着调整调用方式,改用响应式 getter:

TypeScript
1// Reacts to changes in props.userId. No `computed` needed!2const { data: projects } = useUserProjects(() => props.userId)

这为我们提供了简洁的语法和所需的响应性,且无任何不必要的记忆化开销。

其他受追踪的查询选项

上文仅讨论了其中一个会追踪响应式依赖的查询选项。但除 queryKey 外,enabled 同样支持使用响应式值。当你需要基于某些派生状态控制查询的拉取行为时,这一能力尤为有用:

TypeScript
1export function useUserProjects(userId: MaybeRef<string>) {2  return useQuery({3    queryKey: ['userProjects', userId],4    queryFn: () => api.fetchUserProjects(toValue(userId)),5    enabled: () => toValue(userId) === activeUserId.value,6  })7}

有关该选项的更多细节,请参阅 useQuery 参考文档 页面。

其余查询选项也接受 ref,并能响应其值的变化。但响应式 getter 仅在 queryKey 和 enabled 中被解析,因为对 select 或 staleTime 等选项而言,函数本身已是合法的值。

不可变性

useQuery 返回的结果始终不可变。这是出于性能与缓存目的所必需的设计。若你需要修改 useQuery 返回的值,必须先创建该数据的副本。

该设计带来一个直接影响:将 useQuery 返回的值直接用于 v-model 这类双向绑定将无法正常工作。你必须先创建一个可变副本,再尝试就地更新它。

关键要点

  • 所有查询选项均接受 ref,但只有 queryKey 和 enabled 同时支持响应式 getter。
  • 查询选项应支持 Vue 中全部三类值:ref、普通值和响应式 getter。
  • 若你期望查询能响应其所消费值的变化,请确保这些值是响应式的(即:直接向查询传入 ref,或使用响应式 getter)。
  • 若无需查询具备响应性,则传入普通值即可。
  • 对于简单的派生状态(如属性访问),建议使用响应式 getter 替代 computed。
  • useQuery 返回的结果始终不可变。

本页为 TanStack Query 文档(Vue 版)的非官方中文翻译,原文由上游团队维护,遵循 MIT 许可。 译文如有偏差,以英文原文为准。

译自上游 59d11de