Vue Query 支持在服务器上预取多个查询,然后将这些查询_脱水_到 queryClient。这意味着服务器可以预渲染标记,该标记在页面加载时立即可用,并且一旦 JS 可用,Vue Query 就可以使用库的全部功能来升级或_水合_这些查询。这包括在客户端重新获取这些查询(如果它们自服务器上渲染以来已过时)。
首先在您的 plugins 目录中创建一个 vue-query.ts 文件,其中包含以下内容:
import type {
DehydratedState,
VueQueryPluginOptions,
} from '@tanstack/vue-query'
import {
VueQueryPlugin,
QueryClient,
hydrate,
dehydrate,
} from '@tanstack/vue-query'
// Nuxt 3 应用别名
import { defineNuxtPlugin, useState } from '#imports'
export default defineNuxtPlugin((nuxt) => {
const vueQueryState = useState<DehydratedState | null>('vue-query')
// 在此处修改您的 Vue Query 全局设置
const queryClient = new QueryClient({
defaultOptions: { queries: { staleTime: 5000 } },
})
const options: VueQueryPluginOptions = { queryClient }
nuxt.vueApp.use(VueQueryPlugin, options)
if (import.meta.server) {
nuxt.hooks.hook('app:rendered', () => {
vueQueryState.value = dehydrate(queryClient)
})
}
if (import.meta.client) {
hydrate(queryClient, vueQueryState.value)
}
})
import type {
DehydratedState,
VueQueryPluginOptions,
} from '@tanstack/vue-query'
import {
VueQueryPlugin,
QueryClient,
hydrate,
dehydrate,
} from '@tanstack/vue-query'
// Nuxt 3 应用别名
import { defineNuxtPlugin, useState } from '#imports'
export default defineNuxtPlugin((nuxt) => {
const vueQueryState = useState<DehydratedState | null>('vue-query')
// 在此处修改您的 Vue Query 全局设置
const queryClient = new QueryClient({
defaultOptions: { queries: { staleTime: 5000 } },
})
const options: VueQueryPluginOptions = { queryClient }
nuxt.vueApp.use(VueQueryPlugin, options)
if (import.meta.server) {
nuxt.hooks.hook('app:rendered', () => {
vueQueryState.value = dehydrate(queryClient)
})
}
if (import.meta.client) {
hydrate(queryClient, vueQueryState.value)
}
})
现在您已准备好使用 onServerPrefetch 在您的页面中预取一些数据。
export default defineComponent({
setup() {
const { data, suspense } = useQuery({
queryKey: ['test'],
queryFn: fetcher,
})
onServerPrefetch(async () => {
await suspense()
})
return { data }
},
})
export default defineComponent({
setup() {
const { data, suspense } = useQuery({
queryKey: ['test'],
queryFn: fetcher,
})
onServerPrefetch(async () => {
await suspense()
})
return { data }
},
})
首先在您的 plugins 目录中创建一个 vue-query.js 文件,其中包含以下内容:
import Vue from 'vue'
import { VueQueryPlugin, QueryClient, hydrate } from '@tanstack/vue-query'
export default (context) => {
// 在此处修改您的 Vue Query 全局设置
const queryClient = new QueryClient({
defaultOptions: { queries: { staleTime: 5000 } },
})
if (process.server) {
context.ssrContext.VueQuery = queryClient
}
if (process.client) {
Vue.use(VueQueryPlugin, { queryClient })
if (context.nuxtState && context.nuxtState.vueQueryState) {
hydrate(queryClient, context.nuxtState.vueQueryState)
}
}
}
import Vue from 'vue'
import { VueQueryPlugin, QueryClient, hydrate } from '@tanstack/vue-query'
export default (context) => {
// 在此处修改您的 Vue Query 全局设置
const queryClient = new QueryClient({
defaultOptions: { queries: { staleTime: 5000 } },
})
if (process.server) {
context.ssrContext.VueQuery = queryClient
}
if (process.client) {
Vue.use(VueQueryPlugin, { queryClient })
if (context.nuxtState && context.nuxtState.vueQueryState) {
hydrate(queryClient, context.nuxtState.vueQueryState)
}
}
}
将此插件添加到您的 nuxt.config.js
module.exports = {
...
plugins: ['~/plugins/vue-query.js'],
}
module.exports = {
...
plugins: ['~/plugins/vue-query.js'],
}
现在您已准备好使用 onServerPrefetch 在您的页面中预取一些数据。
// pages/todos.vue
<template>
<div>
<button @click="refetch">重新获取</button>
<p>{{ data }}</p>
</div>
</template>
<script lang="ts">
import {
defineComponent,
onServerPrefetch,
useContext,
} from '@nuxtjs/composition-api'
import { useQuery, useQueryClient, dehydrate } from '@tanstack/vue-query'
export default defineComponent({
setup() {
// 从 SSR 上下文或 Vue 上下文获取 QueryClient
const { ssrContext } = useContext()
// 确保将 `queryClient` 作为第二个参数传递给 `useQuery` 调用
const queryClient =
(ssrContext != null && ssrContext.VueQuery) || useQueryClient()
// 这将被预取并从服务器发送
const { data, refetch, suspense } = useQuery(
{
queryKey: ['todos'],
queryFn: getTodos,
},
queryClient,
)
// 这不会被预取,它将在客户端开始获取
const { data2 } = useQuery(
{
queryKey: 'todos2',
queryFn: getTodos,
},
queryClient,
)
onServerPrefetch(async () => {
await suspense()
ssrContext.nuxt.vueQueryState = dehydrate(queryClient)
})
return {
refetch,
data,
}
},
})
</script>
// pages/todos.vue
<template>
<div>
<button @click="refetch">重新获取</button>
<p>{{ data }}</p>
</div>
</template>
<script lang="ts">
import {
defineComponent,
onServerPrefetch,
useContext,
} from '@nuxtjs/composition-api'
import { useQuery, useQueryClient, dehydrate } from '@tanstack/vue-query'
export default defineComponent({
setup() {
// 从 SSR 上下文或 Vue 上下文获取 QueryClient
const { ssrContext } = useContext()
// 确保将 `queryClient` 作为第二个参数传递给 `useQuery` 调用
const queryClient =
(ssrContext != null && ssrContext.VueQuery) || useQueryClient()
// 这将被预取并从服务器发送
const { data, refetch, suspense } = useQuery(
{
queryKey: ['todos'],
queryFn: getTodos,
},
queryClient,
)
// 这不会被预取,它将在客户端开始获取
const { data2 } = useQuery(
{
queryKey: 'todos2',
queryFn: getTodos,
},
queryClient,
)
onServerPrefetch(async () => {
await suspense()
ssrContext.nuxt.vueQueryState = dehydrate(queryClient)
})
return {
refetch,
data,
}
},
})
</script>
如演示所示,预取某些查询并让其他查询在 queryClient 上获取是可以的。这意味着您可以通过为特定查询添加或删除 prefetchQuery 或 suspense 来控制服务器渲染的内容。
将 VueQuery 客户端状态与 vite-ssr 同步,以便将其序列化到 DOM 中:
// main.js (入口点)
import App from './App.vue'
import viteSSR from 'vite-ssr/vue'
import {
QueryClient,
VueQueryPlugin,
hydrate,
dehydrate,
} from '@tanstack/vue-query'
export default viteSSR(App, { routes: [] }, ({ app, initialState }) => {
// -- 这是 Vite SSR 主钩子,每个请求调用一次
// 创建一个新的 VueQuery 客户端
const queryClient = new QueryClient()
// 将 initialState 与客户端状态同步
if (import.meta.env.SSR) {
// 指示如何在 SSR 期间访问和序列化 VueQuery 状态
initialState.vueQueryState = { toJSON: () => dehydrate(queryClient) }
} else {
// 在浏览器中重用现有状态
hydrate(queryClient, initialState.vueQueryState)
}
// 挂载并将客户端提供给应用程序组件
app.use(VueQueryPlugin, { queryClient })
})
// main.js (入口点)
import App from './App.vue'
import viteSSR from 'vite-ssr/vue'
import {
QueryClient,
VueQueryPlugin,
hydrate,
dehydrate,
} from '@tanstack/vue-query'
export default viteSSR(App, { routes: [] }, ({ app, initialState }) => {
// -- 这是 Vite SSR 主钩子,每个请求调用一次
// 创建一个新的 VueQuery 客户端
const queryClient = new QueryClient()
// 将 initialState 与客户端状态同步
if (import.meta.env.SSR) {
// 指示如何在 SSR 期间访问和序列化 VueQuery 状态
initialState.vueQueryState = { toJSON: () => dehydrate(queryClient) }
} else {
// 在浏览器中重用现有状态
hydrate(queryClient, initialState.vueQueryState)
}
// 挂载并将客户端提供给应用程序组件
app.use(VueQueryPlugin, { queryClient })
})
然后,使用 Vue 的 onServerPrefetch 从任何组件调用 VueQuery:
<!-- MyComponent.vue -->
<template>
<div>
<button @click="refetch">重新获取</button>
<p>{{ data }}</p>
</div>
</template>
<script setup>
import { useQuery } from '@tanstack/vue-query'
import { onServerPrefetch } from 'vue'
// 这将被预取并从服务器发送
const { refetch, data, suspense } = useQuery({
queryKey: ['todos'],
queryFn: getTodos,
})
onServerPrefetch(suspense)
</script>
<!-- MyComponent.vue -->
<template>
<div>
<button @click="refetch">重新获取</button>
<p>{{ data }}</p>
</div>
</template>
<script setup>
import { useQuery } from '@tanstack/vue-query'
import { onServerPrefetch } from 'vue'
// 这将被预取并从服务器发送
const { refetch, data, suspense } = useQuery({
queryKey: ['todos'],
queryFn: getTodos,
})
onServerPrefetch(suspense)
</script>
任何带有错误的查询都会自动从脱水中排除。这意味着默认行为是假装这些查询从未在服务器上加载,通常会显示加载状态,并在 queryClient 上重试查询。无论错误如何,都会发生这种情况。
有时这种行为是不可取的,您可能希望在某些错误或查询上使用正确的状态代码呈现错误页面。在这些情况下,使用 fetchQuery 并捕获任何错误以手动处理这些错误。
查询是否过时取决于其 dataUpdatedAt 的时间。需要注意的是,服务器需要有正确的时间才能正常工作,但使用的是 UTC 时间,因此时区不会影响这一点。
由于 staleTime 默认为 0,因此默认情况下,查询将在页面加载时在后台重新获取。您可能希望使用更高的 staleTime 来避免这种双重获取,尤其是在您不缓存标记的情况下。
这种过时查询的重新获取与在 CDN 中缓存标记完美匹配!您可以将页面本身的缓存时间设置得相当高,以避免在服务器上重新渲染页面,但将查询的 staleTime 配置得较低,以确保在用户访问页面后立即在后台重新获取数据。也许您想将页面缓存一周,但如果数据超过一天,则在页面加载时自动重新获取数据?
如果您为每个请求创建 QueryClient,Vue Query 会为此客户端创建隔离的缓存,该缓存会在 gcTime 期间保留在内存中。如果在该期间内请求数量较多,这可能会导致服务器上的高内存消耗。
在服务器上,gcTime 默认为 Infinity,这会禁用手动垃圾回收,并在请求完成后自动清除内存。如果您显式设置了非 Infinity 的 gcTime,则您将负责尽早清除缓存。
要在不再需要缓存后清除缓存并降低内存消耗,您可以在处理请求并将脱水状态发送到客户端后添加对 queryClient.clear() 的调用。
或者,您可以设置一个较小的 gcTime。