Vue 3 组合式 API:从 Options 迁移到 script setup

导语

维护一个 Vue 2 老项目时,你大概遇到过这样的场景:一个组件几百行,datamethodscomputedwatch 被选项区块切得七零八落,相关的逻辑散落在五六个区块里。想复用一段"计数器 + 定时刷新"的逻辑,只能复制粘贴,或者祭出 mixins------结果命名冲突、来源不清。Vue 3 的 Composition API 正是为解决这类问题而来。本文用一条渐进式路径,带你从 Options API 平滑迁移到 <script setup>,不重写、不掉档。

摘要 :本文梳理从 Options API 迁移到 Composition API 的完整路径:先用 setup() 桥接混用,再逐项改写各选项区块,接着落地 <script setup> 语法糖,重点拆解 ref/reactive 与解构陷阱,最后用 composables 替代 mixins 做逻辑复用。附对照表、迁移流程图与可运行示例。

文章目录

    • 导语
    • [一、为什么要迁移:Options API 的痛点与 Composition API 的优势](#一、为什么要迁移:Options API 的痛点与 Composition API 的优势)
    • [二、渐进式迁移策略:用 setup() 桥接,新老 API 混用](#二、渐进式迁移策略:用 setup() 桥接,新老 API 混用)
    • [三、语法逐项对照:Options 各区块如何改写为 Composition](#三、语法逐项对照:Options 各区块如何改写为 Composition)
    • [四、`<script setup>` 语法糖:更简洁的组件写法](#四、<script setup> 语法糖:更简洁的组件写法)
    • [五、响应式核心与高频坑:ref vs reactive 与解构陷阱](#五、响应式核心与高频坑:ref vs reactive 与解构陷阱)
    • [六、逻辑复用升级:从 mixins 到 composables](#六、逻辑复用升级:从 mixins 到 composables)
    • 总结

一、为什么要迁移:Options API 的痛点与 Composition API 的优势

Composition API(组合式 API) 是框架提供的、基于函数的逻辑组织方式,把"按选项分类"换成"按逻辑关注点聚合"。

Options API 把代码按 datamethodscomputedwatch、生命周期五大块平铺。组件小的时候清晰,一旦逻辑变多,同一功能的代码就被拆进不同区块,阅读时要上下反复跳转。更麻烦的是复用:Options 时代主要靠 mixins,多个 mixins 合并后,同名属性会静默覆盖,调试时很难追溯来源。

维度 Options API Composition API
逻辑组织 按选项类型平铺 按逻辑关注点聚合
复用方式 mixins(易冲突) composables(来源清晰)
类型推导 较弱 配合 TS 更友好
代码跳转 需跨区块查找 相关逻辑集中一处

官方 FAQ 也点明两个核心收益:better logic reuse(更好的逻辑复用)与 more flexible code organization(更灵活的组织方式)参考 官方文档。需要强调的是,迁移不是"二选一"------框架同时兼容两种风格,老代码可以继续跑。

二、渐进式迁移策略:用 setup() 桥接,新老 API 混用

最稳妥的迁移不是推倒重来,而是在现有 Options 组件里用 setup() 引入 Composition 逻辑。框架允许在 Options 组件内声明 setup(),它的返回值会合并进组件实例 ,与 datamethods 共存。这样你可以只把最痛的那段逻辑改成 Composition,其余暂不动。

vue 复制代码
<script>
import { ref, onMounted } from 'vue'

export default {
  props: { title: String },
  data() {
    return { count: 0 }
  },
  setup(props) {
    // Composition 逻辑:响应式引用 + 生命周期
    const message = ref('Hello Composition')
    onMounted(() => {
      console.log('setup 内注册的生命周期已触发')
    })
    // 返回的对象会合并进组件实例,模板可直接使用
    return { message }
  }
}
</script>

上面这个组件,data 里的 countsetup 里的 message 在模板中都能直接用,互不干扰。这是新老混用的关键过渡形态。

推荐的迁移路径如下,每一步都可独立提交、独立验证:

三、语法逐项对照:Options 各区块如何改写为 Composition

进入全量改写阶段,逐一把选项区块映射到 Composition 函数。下面先给一张速查表,再给改写前后对比。

Options API Composition API
data() ref() / reactive()
methods 普通函数
computed computed()
watch / watch:{} watch() / watchEffect()
created / mounted onMounted()onXxx
props defineProps()<script setup>
emits defineEmits()<script setup>

改写前(纯 Options):

vue 复制代码
<script>
export default {
  data() {
    return { count: 0 }
  },
  computed: {
    doubleCount() { return this.count * 2 }
  },
  watch: {
    count(newVal) { console.log('count changed', newVal) }
  },
  methods: {
    increment() { this.count++ }
  },
  mounted() {
    console.log('mounted')
  }
}
</script>

改写后(Composition,仍用 setup() 导出):

vue 复制代码
<script>
import { ref, computed, watch, onMounted } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const doubleCount = computed(() => count.value * 2)
    watch(count, (newVal) => console.log('count changed', newVal))
    const increment = () => { count.value++ }
    onMounted(() => console.log('mounted'))
    return { count, doubleCount, increment }
  }
}
</script>

注意 ref 在 JS 里要用 .value 读写,模板里自动解包;thissetup() 中已不存在,所有状态通过变量直接引用。

四、<script setup> 语法糖:更简洁的组件写法

当组件完全转向 Composition,每次都写 setup() + return 很啰嗦。于是有了 <script setup> 这个编译时语法糖:在 <script> 上加 setup 属性,顶层绑定自动暴露给模板,无需 return

它的几个关键点:

  • 顶层声明的变量/函数自动暴露到模板;
  • 模板里的 ref 自动解包 ,不用 .value
  • defineProps / defineEmits 是编译宏,无需 import;
  • defineOptions(Vue 3.3+)可在 <script setup> 里声明组件名等选项。

完整 SFC 示例:

vue 复制代码
<script setup>
import { ref, computed, onMounted } from 'vue'

const props = defineProps({ title: String })
const emit = defineEmits(['change'])

const count = ref(0)
const doubleCount = computed(() => count.value * 2)

const increment = () => {
  count.value++
  emit('change', count.value)
}

onMounted(() => console.log('组件已挂载'))
</script>

<template>
  <h2>{{ title }}</h2>
  <p>count: {{ count }} / double: {{ doubleCount }}</p>
  <button @click="increment">+1</button>
</template>

这就是生产环境最常见的写法:逻辑集中在 <script setup>,模板只负责渲染。

五、响应式核心与高频坑:ref vs reactive 与解构陷阱

响应式(reactivity) 指数据变化自动驱动视图更新。框架提供两个核心 API:ref(可包装任意类型,推荐作为主力)与 reactive(仅限对象/数组类型,底层是 ES Proxy)。

官方建议以 ref 为主 API。原因正藏在下一个坑里:reactive 返回的是 Proxy 对象,一旦解构就会丢失响应式。

javascript 复制代码
// ❌ 错误:直接解构 reactive,响应式丢失
import { reactive } from 'vue'
const state = reactive({ x: 0, y: 0 })
let { x, y } = state   // x、y 变成普通值,修改不再触发更新
javascript 复制代码
// ✅ 正确:用 toRefs 保持响应式
import { reactive, toRefs } from 'vue'
const state = reactive({ x: 0, y: 0 })
const { x, y } = toRefs(state)  // x、y 仍是 ref,修改会驱动更新

另一个细节:ref 放在数组或 reactive 对象里时,在 JS 中不会自动解包 ,仍需 .value。所以日常开发更省心的选择是用 ref 统一管理,配合 toRefs 在需要解构时保活。

对比项 ref reactive
支持类型 任意(基本/对象) 仅对象/数组
访问方式 .value(JS 中) 直接访问属性
解构 toRefs 保活 直接解构会丢失响应式
官方定位 推荐主力 API 对象场景的便捷封装

关于响应式的底层原理,可进一步阅读 官方响应式文档

六、逻辑复用升级:从 mixins 到 composables

composables(组合式函数) 指以 use 开头命名的普通 JS 函数,内部用 Composition API 封装一段可复用状态与逻辑。它替代了 mixins:没有命名冲突,每个状态的来源都清晰可见。

一个最小可用的 useCounter

javascript 复制代码
// composables/useCounter.js
import { ref, computed } from 'vue'

export function useCounter(initial = 0, step = 1) {
  const count = ref(initial)
  const double = computed(() => count.value * 2)
  const increment = () => { count.value += step }
  const decrement = () => { count.value -= step }
  return { count, double, increment, decrement }
}

在组件里复用,多个组件之间状态互不污染:

vue 复制代码
<script setup>
import { useCounter } from './composables/useCounter'

const { count, double, increment, decrement } = useCounter(0, 1)
</script>

<template>
  <p>{{ count }} / {{ double }}</p>
  <button @click="increment">+</button>
  <button @click="decrement">-</button>
</template>

相比 mixins 的"隐式合并",composables 显式返回、显式解构,来源一目了然。生态上,VueUse 提供了大量开箱即用的组合函数(如鼠标位置、剪贴板、防抖等),可以直接引入,省去重复造轮子。

总结

迁移的本质不是"换写法",而是"换组织逻辑的方式"。建议路线:先用 setup() 在老组件里桥接,再逐项改写选项区块,接着用 <script setup> 收口,最后把可复用逻辑抽成 composables。过程中牢记 ref 为主、reactive 解构需 toRefs 保活,就能避开大部分响应式陷阱。框架同时兼容两种风格,你完全可以按照自己的节奏逐步推进,不必一步到位。

更多组合式 API 与 <script setup> 实战内容,可前往 CSDN 标签聚合页 查阅相关优质文章。


© 2026 | 转载请注明出处

相关推荐
知了清语14 分钟前
以测试为盾,重构为矛——老旧前端系统迭代中的“测试先行”实践总结
前端
攀小黑1 小时前
vue3+Web Speech API 封装了一个弹窗语音输入
前端·macos·xcode
roamingcode1 小时前
4.5 小时,从一句话需求到可安装的 Chrome 插件:一次 AI 结对开发的完整复盘
前端·人工智能·chrome·claude·codex
meilindehuzi_a1 小时前
LangChain.js 对话 Memory 实战:History 持久化、截断与摘要压缩
java·javascript·langchain
江畔柳前堤1 小时前
具身智能全景深度指南(2026年9月版):从“会聊天的AI“到“能干活的机器“
大数据·javascript·图像处理·人工智能·分布式·智慧城市·原型模式
️学习的小王1 小时前
Windows Claude Code 接入 Playwright‑MCP,调用本机Edge浏览器(避坑完整教程)
前端·windows·edge
liuyicenysabel1 小时前
Grafana + Prometheus 分级告警配置设计(P0/P1/P2)
javascript·grafana·prometheus
律宏阔1 小时前
CloakBrowser 开发踩坑笔记
前端·浏览器
Made in Haven7122 小时前
HTML课程笔记补2
前端·笔记·html