Nuxt Bridge 迁移指南:从 `@nuxtjs/composition-api` 升级到 Nuxt 3 兼容的 Composition API
本篇技术指南面向正运行 Nuxt 2 +
@nuxtjs/composition-api、并计划通过 Nuxt Bridge 平滑过渡到 Nuxt 3 的项目。文章以@nuxtjs/composition-api的旧式组合式函数为对照基线,逐一讲解ssrRef、useFetch、useStore、useContext、wrapProperty、useMeta等旧 API 应如何迁移到useState、useLazyAsyncData、useLazyFetch、useNuxtApp、useHead等新 API,并延伸讲解显式导入#imports与关闭自动导入的配置方法。读完本文,你将获得一份可直接对照修改的迁移路线图与逐行 diff 示例。
本文对应仓库文档为 docs/6.bridge/5.nuxt3-compatible-api.md。在 Nuxt Bridge 的升级路径(参见 6.bridge/1.overview.md)中,"Migrate New Composition API" 是关键一环:从 @nuxtjs/composition-api 迁移到 Nuxt 3 兼容的 API 后,将来真正升级 Nuxt 3 时几乎无需再改写代码。除迁移对照外,正文会结合当前 Nuxt 仓库的源码实现(如 state.ts)说明新 API 的底层工作方式,帮助你理解“为什么要这样改”。
迁移概览:一张 API 对照表
以下速查表总结了 @nuxtjs/composition-api 常用导出与 Nuxt 3 兼容 API 的对应关系,后文将对每一项给出详细说明与迁移示例。
@nuxtjs/composition-api |
Nuxt 3 / Nuxt Bridge 兼容方案 | 状态 |
|---|---|---|
ssrRef / shallowSsrRef |
useState(key, init) |
以新组合式函数替代,需显式提供 key |
ssrPromise |
无替代实现 | 已移除,需自行寻找替代方案 |
onGlobalSetup |
defineNuxtPlugin + nuxtApp.hook('vue:setup', ...) |
已移除,改用其他途径 |
useStore |
useNuxtApp().$store |
需改写 |
useContext / withContext |
useNuxtApp() 解构 |
需改写 |
wrapProperty |
自实现(见下文代码) | 不再内置提供 |
useAsync |
useLazyAsyncData(key, handler) |
API 完全不同,需重写 |
useFetch |
useLazyFetch(url) |
API 完全不同,需重写 |
useMeta |
useNuxt2Meta()(仅 Bridge)/ useHead()(需开启 bridge.meta) |
二选一 |
ssrRef 与 shallowSsrRef:统一收敛为 useState
这两个旧函数在 Nuxt 3 兼容层中被一个工作方式非常相似的新组合式函数取代:useState。
关键差异有两点:
- 必须为状态提供一个 key。旧 API 中 key 由 Nuxt 自动生成,而
useState要求显式传入。 - 调用环境受限。
useState只能在 Nuxt 3 插件(由defineNuxtPlugin定义)或组件实例内调用,不能用于全局/环境上下文(ambient context),原因在于全局共享状态存在跨请求串扰(shared state across requests)的危险——多个用户请求共用同一份状态将导致数据泄漏。
迁移示例(diff):
- import { ssrRef } from '@nuxtjs/composition-api'
- const ref1 = ssrRef('initialData')
- const ref2 = ssrRef(() => 'factory function')
+ const ref1 = useState('ref1-key', () => 'initialData')
+ const ref2 = useState('ref2-key', () => 'factory function')
// accessing the state
console.log(ref1.value)
注意 init 既可以返回普通值,也可以返回一个工厂函数;状态读取方式不变,仍是 xxx.value。
由于状态以 key 为标识,只要使用相同的 key,你就能在任意多个位置访问同一份状态——这正是旧 API 自动生成的随机 key 无法提供的特性。详细用法见仓库中的 useState API 文档。
源码视角:useState 的 key 与 payload
在当前仓库的 packages/nuxt/src/app/composables/state.ts 中可以看到 useState 的实现骨架:
- 所有状态都以
$s为前缀挂载在nuxtApp.payload.state下(const state = toRef(nuxtApp.payload.state, key)),因此状态天然可以跨端(服务端渲染出的状态会被序列化到 payload,供客户端 hydration 时恢复); - 首次访问且值仍为
undefined时才会调用init工厂函数生成初始值; init若返回一个ref,Nuxt 会直接把该 ref 存入 payload,实现“浅响应式”的按需优化——这也是文档建议在状态包含大对象/大数组时配合shallowRef提升性能的原因(见 useState 文档中的shallowRef小节)。
另外,useState 中的状态最终会经过 JSON 序列化写入 payload,因此不要在其中存放无法序列化的内容(如 class 实例、函数、symbol)。
ssrPromise:已移除
ssrPromise 函数在新 API 中已被移除,如果你此前在使用它,需要自行寻找替代实现。若你确实存在必须使用 ssrPromise 的业务场景,建议通过 Nuxt 官方讨论区提出,以便官方评估是否在后续版本中提供等价能力。迁移时不要试图寻找同名函数,而是用 useState、useAsyncData/useLazyAsyncData 等手段重新组织“仅服务端执行一次”的逻辑。
onGlobalSetup:改由插件 + vue:setup 钩子实现
onGlobalSetup 已被移除,但其使用场景可以通过在 defineNuxtPlugin 内调用 useNuxtApp 或 useState 来满足;你同样可以把任意自定义代码放进某个布局(layout)的 setup() 函数中执行。
迁移示例(diff):
- import { onGlobalSetup } from '@nuxtjs/composition-api'
- export default () => {
- onGlobalSetup(() => {
+ export default defineNuxtPlugin((nuxtApp) => {
+ nuxtApp.hook('vue:setup', () => {
// ...
})
- }
+ })
源码视角:vue:setup 运行时钩子
“nuxtApp.hook('vue:setup') 在全局 setup 阶段执行”的说法在当前仓库中可以得到印证:在 packages/nuxt/src/app/nuxt.ts 的运行时钩子类型中定义了 'vue:setup': () => void;而根组件 packages/nuxt/src/app/components/nuxt-root.vue 中通过 nuxtApp.hooks.callHookWith(..., 'vue:setup', []) 真正触发该钩子。同时注意钩子回调必须是同步的(诊断项 NUXT_E1011,见 packages/nuxt/src/app/diagnostics/core.ts),异步工作应移入 app:created 等其他钩子。
useStore:通过 useNuxtApp().$store 访问
为了访问 Vuex store 实例,只需解构 useNuxtApp() 返回的上下文中的 $store 即可:
- import { useStore } from '@nuxtjs/composition-api'
+ const { $store } = useNuxtApp()
useContext 与 withContext:使用 useNuxtApp
旧 API 通过 useContext() 取到的注入辅助函数(例如 $axios 之类在 Nuxt 2 插件中通过 app.$axios 暴露的实例),现在一律改为从 useNuxtApp() 中解构:
- import { useContext } from '@nuxtjs/composition-api'
+ const { $axios } = useNuxtApp()
::note
useNuxtApp() 额外暴露了一个名为 nuxt2Context 的 key,其中包含从 Nuxt 2 context 中能取到的全部属性。但不建议直接使用它——nuxt2Context 在 Nuxt 3 中并不存在,依赖它会让代码在真正升级 Nuxt 3 时再次返工。请优先寻找其他访问途径;若实在没有,可在 Nuxt 官方提交 feature request 或发起讨论。
::
useNuxtApp 的完整能力(provide、hook、callHook、payload、runWithContext 等)可参考仓库中的 useNuxtApp API 文档;其实现位于 packages/nuxt/src/app/nuxt.ts,当运行环境中没有 Nuxt 上下文时会抛出异常(对应诊断码 NUXT_E1001)。
wrapProperty:不再提供,给出等价自实现
wrapProperty 辅助函数已不再提供,但可以非常简单地用下面的代码等价替换:
import { computed, getCurrentInstance } from 'vue'
const wrapProperty = (property: string, makeComputed = true) => () => {
const vm = getCurrentInstance().proxy
return makeComputed ? computed(() => vm[property]) : vm[property]
}
它的行为与旧版一致:包装一个实例属性名,默认返回计算属性(makeComputed = true 时惰性求值并保持响应性),传 false 则直接返回当前值。
useAsync 与 useFetch:改用 useLazyAsyncData / useLazyFetch
这两个旧组合式函数可以分别替换为 useLazyAsyncData 和 useLazyFetch。替换后依然保持“在客户端不阻塞路由导航”的行为——这正是名称中 lazy 的含义(数据在后台加载,页面先完成导航渲染)。
::important
尽管新旧名称听上去很像,API 签名是完全不同的。尤其注意:不要像旧 useFetch 那样在组合式函数内部对外部变量赋值(副作用式写法),新 API 一律通过返回值来驱动状态。
::
::warning
在 Nuxt Bridge 中使用 useLazyFetch 前,必须先完成 Nitro 的配置与启用——否则请求层尚未接入,useLazyFetch 无法工作。另外,Bridge 场景下 useAsyncData / useFetch(非 lazy 版本)并不可用,这也正是本文推荐 lazy 版本的原因(参见 6.bridge/1.overview.md 中的警告)。
::
从 useAsync 迁移
<script setup>
- import { useAsync } from '@nuxtjs/composition-api'
- const posts = useAsync(() => $fetch('/api/posts'))
+ const { data: posts } = useLazyAsyncData('posts', () => $fetch('/api/posts'))
+ // or, more simply!
+ const { data: posts } = useLazyFetch('/api/posts')
</script>
从 useFetch 迁移
<script setup>
- import { useFetch } from '@nuxtjs/composition-api'
- const posts = ref([])
- const { fetch } = useFetch(() => { posts.value = await $fetch('/api/posts') })
+ const { data: posts, refresh } = useLazyAsyncData('posts', () => $fetch('/api/posts'))
+ // or, more simply!
+ const { data: posts, refresh } = useLazyFetch('/api/posts')
function updatePosts() {
- return fetch()
+ return refresh()
}
</script>
两处迁移的要点相同:旧写法中的“手动重新拉取”函数 fetch() 对应新 API 的 refresh(),数据赋值改为对 data 的解构与读取,不再手工写入外部 ref。数据获取的完整概念与最佳实践可参考 getting-started 中的数据获取指南。
源码视角:lazy 变体是如何实现的
在 Nuxt 3 中,lazy 只是普通版本的一个工厂参数。看当前仓库的实现即可一目了然:
useLazyFetch(packages/nuxt/src/app/composables/fetch.ts)实际是createUseFetch工厂以{ lazy: true }创建出的实例;useLazyAsyncData(packages/nuxt/src/app/composables/asyncData.ts)同样基于createUseAsyncData工厂以lazy: true创建。
因此,迁移时你并不需要重新学习两套 API——lazy 版本返回的数据结构与普通版完全一致(data、refresh、error、status、pending、clear 等字段,详见 useLazyFetch API 文档),区别只在导航是否等待请求完成。在模板中建议根据 status 区分 pending / error / 成功三种状态渲染,可参考 useLazyFetch 文档中的加载态示例。
Meta 处理:useNuxt2Meta 与 useHead
用 useNuxt2Meta 保持 vue-meta 兼容
如需继续以 vue-meta 兼容的方式操作 meta 标签,可以使用 useNuxt2Meta。它在 Nuxt Bridge 中可用(在 Nuxt 3 中不可用):
<script setup>
- import { useMeta } from '@nuxtjs/composition-api'
useNuxt2Meta({
title: 'My Nuxt App',
})
</script>
useNuxt2Meta 同样支持传入计算属性或 ref,meta 值会随之响应式更新:
<script setup>
const title = ref('my title')
useNuxt2Meta({
title,
})
title.value = 'new title'
</script>
::note
在同一个组件中,不要同时使用 useNuxt2Meta() 与 Options API 的 head(),二者并存时行为不可预测。
::
用 useHead 接入 Nuxt 3 风格的 meta
Nuxt Bridge 还提供了一套 Nuxt 3 兼容的 meta 实现,通过 useHead 组合式函数访问。它底层使用 @unhead/vue(而非 vue-meta)来操作 <head>。
<script setup>
- import { useMeta } from '@nuxtjs/composition-api'
useHead({
title: 'My Nuxt App',
})
</script>
启用它需要在 nuxt.config 中显式开启 bridge.meta(这是一项 opt-in 特性,默认关闭):
import { defineNuxtConfig } from '@nuxt/bridge'
export default defineNuxtConfig({
bridge: {
meta: true,
},
})
配置项含义可对照 6.bridge/10.configuration.md 中的 Feature Flags 说明。同样,由于 useHead 与原生 Nuxt 2 的 head() 属性机制不同(一个走 @unhead/vue、一个走 vue-meta),不建议在同一项目里混用两者,以免相互冲突。
useHead 的更多用法(title、meta、link、script 以及各种响应式取值)详见 useHead API 文档 与 SEO 与 Meta 指南。
显式导入:通过 #imports 别名
Bridge/Nuxt 3 会为每个可自动导入的 API 暴露一个 #imports 别名,当你需要把自动导入改为显式导入时可以直接使用:
<script setup lang="ts">
import { computed, ref } from '#imports'
const count = ref(1)
const double = computed(() => count.value * 2)
</script>
这不仅便于阅读代码时明确依赖来源,也是在配合“关闭自动导入”或类型检查工具时保持代码可运行的重要手段。
关闭自动导入
若希望彻底关闭组合式函数与工具函数的自动导入,可在 nuxt.config 中设置 imports.autoImport: false:
export default defineNuxtConfig({
imports: {
autoImport: false,
},
})
注意,这会完全关闭自动导入,但并不会影响显式导入:你依然可以通过上面的 #imports 别名按需导入所需 API。自动导入机制的整体设计(内置自动导入清单、目录扫描、显式导入等)见 guide/concepts 的自动导入概念文档,其中同样记录了 #imports 显式导入与关闭自动导入的等价配置写法。
小结:一次改写,为 Nuxt 3 铺路
Nuxt Bridge 的定位是一个“前向兼容层”,让你在 Nuxt 2 项目上提前体验并采用 Nuxt 3 的 API(详见 6.bridge/1.overview.md)。按本文顺序逐项替换后,你的代码中来自 @nuxtjs/composition-api 的依赖将被清零:SSR 共享状态交给 useState、全局逻辑迁入插件与 vue:setup 钩子、上下文访问统一收敛到 useNuxtApp、数据获取切换为 useLazyAsyncData / useLazyFetch、meta 处理在 useNuxt2Meta 与 useHead 之间按需二选一,最后通过 #imports 显式导入与 imports.autoImport 掌控自动导入粒度。
这样做最大的收益正如本文开头所述:将来从 Bridge 正式迁移到 Nuxt 3 时,几乎不再需要第二次改写。迁移过程不要求一步到位,可以按上述小节逐个渐进完成,每个步骤都可独立验证后再进入下一步。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00