人人都会AI编程

28.4 选项式到组合式 API 的改造路径

更新时间:2026-07-09

从选项式 API(Options API)迁移到组合式 API(Composition API)并不是推倒重来,而是在同一个 Vue 3 项目里,用另一种更灵活的方式重新组织已有的代码。核心目标不是“语法更新”,而是让逻辑更内聚、复用更方便

第一步:理解两种写法的对应关系,而不是死记硬背

先建立一张基础映射表,把选项式中的核心概念“翻译”成组合式的写法:

| 选项式 API | 组合式 API(<script setup>) |
| ------------------- | --------------------------------------------------- |
| data | ref()reactive() |
| computed | computed() |
| watch | watch()watchEffect() |
| methods | 普通函数 |
| mounted | onMounted() |
| props | defineProps() |
| emits | defineEmits() |
| this.xxx | 直接使用变量名,无需 this |

不需要一次性全背下来。先看一个简单组件的改造,体会“去掉 this、逻辑集中”的感觉。

选项式写法:

export default {
  data() {
    return {
      count: 0,
      double: 0
    }
  },
  computed: {
    doubleCount() {
      return this.count * 2
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  mounted() {
    console.log('组件挂载,当前计数:', this.count)
  }
}

组合式写法(<script setup>):

import { ref, computed, onMounted } from 'vue'

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

function increment() {
  count.value++
}

onMounted(() => {
  console.log('组件挂载,当前计数:', count.value)
})

一眼看去,组合式版本将所有与“计数”相关的状态、计算、方法、生命周期写在一起,不再分散在 datacomputedmethods 等不同选项中。这就是组合式 API 最大的心智模型变化:按功能组织,而不是按选项类型组织

第二步:从单个组件开始,渐进改造

你不需要把整个项目一次性全改成组合式 API。Vue 3 完美兼容选项式写法,两者可以在同一个项目中并存。建议采用以下改造节奏:

  1. 先改新组件

所有新增组件直接使用 <script setup> + 组合式 API。这是成本最低的一步,团队成员可以马上开始习惯新写法。

  1. 再改“痛点组件”

项目中总有一些逻辑特别复杂、或者因 Mixin 导致命名冲突和来源不清的组件。这些组件改为组合式 API 后,可以用自定义 Hook 把可复用的逻辑抽出来,可读性和可维护性会立刻提升。典型的改造目标:大量使用 Mixin 的组件、需要通过 $refs 操作子组件逻辑的组件。

  1. 最后统一存量代码

如果项目维护周期还很长,可以逐步把剩余的老组件也改为组合式 API。可以借助官方的迁移工具(如 vue-codemod)批量处理,但人工复查仍然必不可少。

第三步:把 Mixin 改造成自定义 Hook

选项式 API 时代,逻辑复用主要靠 Mixin。但 Mixin 有几个知名痛点:命名冲突、来源不透明、隐式依赖。组合式 API 的自定义 Hook(也叫组合式函数)彻底解决了这些问题。

改造的核心做法:把 Mixin 中的 datacomputedmethods、生命周期勾子,都搬到一个独立的 useXxx 函数里

// 旧写法:一个处理表单验证的 Mixin
const formMixin = {
  data() {
    return { formValid: false }
  },
  methods: {
    validateForm() { /* ... */ }
  }
}

// 新写法:一个自定义 Hook
import { ref } from 'vue'

export function useFormValidation() {
  const formValid = ref(false)
  function validateForm() { /* ... */ }
  return { formValid, validateForm }
}

然后在组件中调用:

const { formValid, validateForm } = useFormValidation()

所有变量和方法的来源一目了然,也不再有命名冲突的风险。

第四步:处理 this 的消失

组合式 API 中没有 this,这会让一些依赖 this 的老代码需要调整。

  • 访问路由和状态管理

以前用 this.$routerthis.$routethis.$store,现在需要使用组合式 API 对应的函数:

  // Vue Router
  import { useRouter, useRoute } from 'vue-router'
  const router = useRouter()
  const route = useRoute()

  // Pinia
  import { useUserStore } from '@/stores/user'
  const userStore = useUserStore()
  
  • 获取 DOM 元素

以前用 this.$refs.xxx,现在改用模板 ref:

  const myDiv = ref(null)
  // 模板中 <div ref="myDiv"></div> 改为 <div ref="myDiv"></div>
  // 访问时用 myDiv.value
  
  • 访问全局属性

如果之前通过 app.config.globalProperties 挂载了全局方法或变量,在组合式 API 中可以使用 getCurrentInstance() 或更推荐的方式:把它们封装成独立的 Hook 或通过 provide/inject 传递。

第五步:利用 <script setup> 语法糖减少样板代码

<script setup> 是组合式 API 的推荐书写方式,它在编译时做处理,省去 setup() 函数的返回值和 export default。顶层的变量和函数自动暴露给模板,写法最为简洁。

<script setup>
import { ref } from 'vue'
const count = ref(0)
function add() { count.value++ }
</script>

如果组件有 Props 和 Emits,使用编译器宏声明:

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

改造中的常见坑点

  • .value 遗忘:在 <script setup> 中访问 ref 的值需要 .value,模板中不需要。写习惯后就好了。
  • 响应式丢失:如果从 reactive 对象中解构出基本类型的属性,会丢失响应式。始终用 toRefs() 解构,或者直接使用 ref
  • 生命周期钩子名称:选项式的 createdbeforeCreate 在组合式中没有直接对应,因为 setup() 就执行在这两个阶段之间,直接在 setup 中写初始化逻辑即可。

一个关键心态:好坏标准不是“用组合式 API”,而是“代码是否更好维护”

选项式 API 本身并没有被淘汰,Vue 官方也明确表示它会继续存在。改造的目的不是追求时髦,而是当你的组件逻辑变得复杂、混入难以追踪时,组合式 API 能给你一个更清晰的解。如果一个小组件用选项式已经非常清晰,甚至可以不动它。

一句话总结改造路径:从新组件开始用 <script setup>,把 Mixin 拆成 Hook,遇到 this 换成对应的组合式函数,保持渐进,按需迁移。