响应性

Vue 使用信号范式来处理和跟踪响应性。此系统的一个关键特性是响应式系统仅在专门监视的响应式属性上触发更新。由此产生的一个结果是,您还需要确保在它们使用的值更新时查询也会更新。

保持查询响应性

当为查询创建组合式函数时,您的首选可能是这样编写

ts
export function useUserProjects(userId: string) {
  return useQuery(
    queryKey: ['userProjects', userId],
    queryFn: () => api.fetchUserProjects(userId),
  );
}
export function useUserProjects(userId: string) {
  return useQuery(
    queryKey: ['userProjects', userId],
    queryFn: () => api.fetchUserProjects(userId),
  );
}

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

ts
// Reactive user ID ref.
const userId = ref('1')
// Fetches the user 1's projects.
const { data: projects } = useUserProjects(userId.value)

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

const onChangeUser = (newUserId: string) => {
  // Edits the userId, but the query will not re-fetch.
  userId.value = newUserId
}

此代码将无法按预期工作。这是因为我们直接从 userId ref 中提取值。Vue-query 没有跟踪 userId ref,因此它无法知道该值何时更改。

幸运的是,对此的修复非常简单。该值必须在查询键中变为可跟踪的。我们可以直接在组合式函数中接受 ref 并将其放在查询键中

ts
export function useUserProjects(userId: Ref<string>) {
  return useQuery(
    queryKey: ['userProjects', userId],
    queryFn: () => api.fetchUserProjects(userId.value),
  );
}
export function useUserProjects(userId: Ref<string>) {
  return useQuery(
    queryKey: ['userProjects', userId],
    queryFn: () => api.fetchUserProjects(userId.value),
  );
}

现在,当 userId 更改时,查询将重新获取。

ts
const onChangeUser = (newUserId: string) => {
  // Query refetches data with new user ID!
  userId.value = newUserId
}
const onChangeUser = (newUserId: string) => {
  // Query refetches data with new user ID!
  userId.value = newUserId
}

在 vue query 中,查询键内的任何响应式属性都会自动跟踪更改。这允许 vue-query 在给定请求的参数更改时重新获取数据。

考虑非响应式查询

虽然不太可能,但有时传递非响应式变量是有意的。例如,某些实体只需要获取一次,不需要跟踪,或者我们在突变后使查询选项对象失效。如果我们使用上面定义的自定义组合式函数,在这种情况下使用起来感觉有点不对劲

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

我们必须创建一个中间 ref 只是为了使参数类型兼容。我们可以做得更好。让我们更新我们的组合式函数以同时接受普通值和响应式值

ts
export function useUserProjects(userId: MaybeRef<string>) {
  return useQuery(
    queryKey: ['userProjects', userId],
    queryFn: () => api.fetchUserProjects(toValue(userId)),
  );
}
export function useUserProjects(userId: MaybeRef<string>) {
  return useQuery(
    queryKey: ['userProjects', userId],
    queryFn: () => api.fetchUserProjects(toValue(userId)),
  );
}

现在我们可以将组合式函数与普通值和 refs 一起使用

ts
// Fetches the user 1's projects, userId is not expected to change.
const { data: projects } = useUserProjects('1')

// Fetches the user 1's projects, queries will react to changes on userId.
const userId = ref('1')

// Make some changes to userId...

// Query re-fetches based on any changes to userId.
const { data: projects } = useUserProjects(userId)
// Fetches the user 1's projects, userId is not expected to change.
const { data: projects } = useUserProjects('1')

// Fetches the user 1's projects, queries will react to changes on userId.
const userId = ref('1')

// Make some changes to userId...

// Query re-fetches based on any changes to userId.
const { data: projects } = useUserProjects(userId)

在查询内部使用派生状态

从另一个响应式状态源派生一些新的响应式状态是很常见的。通常,这个问题会在处理组件 props 的情况下显现出来。假设我们的 userId 是传递给组件的 prop

vue
<script setup lang="ts">
const props = defineProps<{
  userId: string
}>()
</script>
<script setup lang="ts">
const props = defineProps<{
  userId: string
}>()
</script>

您可能会想在查询中直接使用 prop,如下所示

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

但是,与第一个示例类似,这不是响应式的。对 响应式 变量的属性访问会导致响应性丢失。我们可以通过使用 computed 使此派生状态变为响应式来修复此问题

ts
const userId = computed(() => props.userId)

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

// Reacts to changes in props.userId.
const { data: projects } = useUserProjects(userId)

这可以按预期工作,但是,此解决方案并不总是最优的。除了引入中间变量之外,我们还创建了一个在某种程度上不必要的记忆值。对于简单属性访问的琐碎情况,computed 是一种优化,但没有真正的好处。在这些情况下,更合适的解决方案是使用 响应式 getter。响应式 getter 只是返回基于某些响应式状态的值的函数,类似于 computed 的工作方式。与 computed 不同,响应式 getter 不会记忆它们的值,因此使其成为简单属性访问的良好候选者。

让我们再次重构我们的组合式函数,但这次我们将使其接受 ref、普通值或响应式 getter

ts
export function useUserProjects(userId: MaybeRefOrGetter<string>) {
  ...
}
export function useUserProjects(userId: MaybeRefOrGetter<string>) {
  ...
}

让我们调整我们的用法,现在使用响应式 getter

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

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

其他跟踪的查询选项

上面,我们只接触了一个跟踪响应式依赖项的查询选项。但是,除了 queryKey 之外,enabled 也允许使用响应式值。这在您想要根据某些派生状态控制查询的获取的情况下非常方便

ts
export function useUserProjects(userId: MaybeRef<string>) {
  return useQuery(
    queryKey: ['userProjects', userId],
    queryFn: () => api.fetchUserProjects(toValue(userId)),
    enabled: () => userId.value === activeUserId.value,
  );
}
export function useUserProjects(userId: MaybeRef<string>) {
  return useQuery(
    queryKey: ['userProjects', userId],
    queryFn: () => api.fetchUserProjects(toValue(userId)),
    enabled: () => userId.value === activeUserId.value,
  );
}

有关此选项的更多详细信息,请访问 useQuery 参考页面。

主要收获

  • enabledqueryKey 是可以接受响应式值的两个查询选项。
  • 在 Vue 中传递接受所有三种类型值的查询选项:refs、普通值和响应式 getter。
  • 如果您希望查询根据其使用的值中的更改做出反应,请确保这些值是响应式的。(即直接将 refs 传递到查询中,或使用响应式 getter)
  • 如果您不需要查询是响应式的,请传入一个普通值。
  • 对于诸如属性访问之类的简单派生状态,请考虑使用响应式 getter 代替 computed