Vue3组合式API实战——从Options API迁移

写过Vue2的人对Options API再熟悉不过了:data里定义数据,methods里写方法,computed里算属性,mounted里初始化。一个组件写下来,逻辑按类型分散在不同选项里。组件小的时候没问题,一旦复杂了------比如一个用户列表页同时管搜索、分页、行内编辑、批量操作------这些逻辑的代码全混在一起,data里一堆变量、methods里一堆函数,改一个功能得上下翻找半天。

组合式API(Composition API)就是来解决这个问题的。截至2026年8月,Vue最新稳定版是3.5.41,3.6已经进入RC阶段(Vapor Mode是这一版的亮点)。<script setup>从3.2开始就是推荐写法,生态已经完全成熟,没有理由再观望了。

两种API到底差在哪

Options API的组织方式是"按选项分类"。同一个功能的响应式数据、计算属性、方法、生命周期钩子,被拆散到不同选项里。组件越大,这种拆散带来的认知负担越重。

组合式API的组织方式是"按功能聚合"。一个功能的全部逻辑------数据、计算、方法、副作用------可以放在同一个地方。需要复用的时候,抽成一个函数,哪个组件要用就调哪个函数。

说白了,Options API是按代码类型组织,组合式API是按业务功能组织。后者更符合人类思考问题的方式。

script setup怎么写

<script setup>是组合式API的语法糖,也是最推荐的写法。对比一下就明白它省了多少事。

先看老写法(普通<script> + setup函数):

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

export default {
  props: {
    title: String
  },
  setup(props) {
    const count = ref(0)
    const double = computed(() => count.value * 2)
    const increment = () => count.value++

    return { count, double, increment }
  }
}
</script>

再看<script setup>:

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

const props = defineProps({
  title: String
})
const count = ref(0)
const double = computed(() => count.value * 2)
const increment = () => count.value++
</script>

省掉了export default、setup函数和return。顶层的变量和函数自动暴露给模板用,不用手动return。defineProps和defineEmits是编译宏,不需要导入,在<script setup>里直接用。

ref和reactive选哪个

这两个都是创建响应式数据的API,区别在于用法和适用场景。

ref包装基本类型和对象都行,访问值要加.value:

js 复制代码
const count = ref(0)
count.value++  // 修改要加.value

const user = ref({ name: '张三', age: 25 })
user.value.name = '李四'  // 也要.value

reactive只能包装对象,返回的是Proxy代理,访问不用加.value:

js 复制代码
const state = reactive({ name: '张三', age: 25 })
state.name = '李四'  // 直接改

模板里用ref不用加.value,Vue自动解包。但JS代码里必须加,这个容易忘,刚上手的时候踩过不少坑。

实际开发中的建议:基本类型用ref,对象类型用哪个都行。但有个坑要注意------reactive返回的对象解构后会失去响应性。const { name } = reactive(...)之后,name就是个普通字符串,改了不会触发更新。要解构又保持响应性,得用toRefs:

js 复制代码
const state = reactive({ name: '张三', age: 25 })
const { name, age } = toRefs(state)  // 解构成ref,保持响应性

团队里统一规范比较好,要么全用ref(心智负担低,都加.value),要么对象用reactive基本类型用ref。别混着来。

computed和watch

computed创建计算属性,跟Options API里的computed概念一样------基于响应式依赖缓存,依赖不变就不重算:

js 复制代码
const firstName = ref('张')
const lastName = ref('三')
const fullName = computed(() => `${firstName.value}${lastName.value}`)

watch监听响应式数据的变化,执行副作用。跟Options API的watch比,组合式API的watch更灵活,可以监听多个数据源:

js 复制代码
// 监听单个ref
watch(count, (newVal, oldVal) => {
  console.log(`从${oldVal}变成${newVal}`)
})

// 监听多个
watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
  // 任一变化都触发
})

// 监听reactive对象的属性,要写getter函数
watch(() => state.age, (newAge) => {
  console.log(`年龄变成${newAge}`)
})

还有个watchEffect,它不需要指定监听谁,函数里用了什么响应式数据就自动监听什么。适合依赖关系复杂的场景,但追踪起来不如watch直观:

js 复制代码
watchEffect(() => {
  console.log(`${state.name}今年${state.age}岁`)  // name或age变化都触发
})

watch是惰性的------数据变化才执行,可以拿到新旧值。watchEffect是立即执行的------一调用就跑一次,但拿不到旧值。按需选。

生命周期钩子变了什么

Options API里的mounted、created这些钩子,在组合式API里变成了onMounted、onBeforeMount等函数。beforeCreate和created的逻辑直接写在setup顶层就行,因为setup本身就是在这两个时机之间执行的。

对应关系很直接:

  • beforeCreate / created → 直接写setup顶层代码

  • beforeMount → onBeforeMount

  • mounted → onMounted

  • beforeUpdate → onBeforeUpdate

  • updated → onUpdated

  • beforeUnmount → onBeforeUnmount

  • unmounted → onUnmounted

js 复制代码
import { onMounted, onUnmounted } from 'vue'

onMounted(() => {
  console.log('组件挂载完成')
  window.addEventListener('resize', handleResize)
})

onUnmounted(() => {
  console.log('组件卸载')
  window.removeEventListener('resize', handleResize)  // 别忘了清理
})

composables:逻辑复用的正确姿势

组合式API最大的价值在逻辑复用。Options API时代复用逻辑靠Mixins,但Mixins的问题是命名冲突和数据来源不透明------三个Mixin混进来,某个变量是谁定义的根本看不出来。

composables就是一个约定:以use开头的函数,接收响应式数据,返回响应式数据和操作方法。看一个实际的例子------封装一个分页逻辑:

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

export function usePagination(fetchFn, pageSize = 20) {
  const currentPage = ref(1)
  const total = ref(0)
  const loading = ref(false)

  const totalPages = computed(() => Math.ceil(total.value / pageSize))

  async function goToPage(page) {
    loading.value = true
    const res = await fetchFn(page, pageSize)
    total.value = res.total
    currentPage.value = page
    loading.value = false
  }

  function nextPage() {
    if (currentPage.value < totalPages.value) {
      goToPage(currentPage.value + 1)
    }
  }

  return { currentPage, total, totalPages, loading, goToPage, nextPage }
}

组件里直接用:

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

const { currentPage, totalPages, loading, goToPage } = usePagination(fetchUsers, 20)
</script>

谁的数据谁的方法,一目了然。多个组件用同一个composable,各自维护独立的响应式状态,互不干扰。这比Mixin干净太多了。

TypeScript集成

组合式API跟TypeScript是天生一对。<script setup>加lang="ts"就能写TS:

vue 复制代码
<script setup lang="ts">
import { ref, computed } from 'vue'

interface User {
  id: number
  name: string
  email: string
}

const users = ref<User[]>([])
const keyword = ref('')
const filteredUsers = computed(() =>
  users.value.filter(u => u.name.includes(keyword.value))
)

// defineProps带类型
const props = defineProps<{
  title: string
  count?: number
}>()

// defineEmits带类型
const emit = defineEmits<{
  (e: 'update', user: User): void
  (e: 'delete', id: number): void
}>()
</script>

defineProps和defineEmits支持泛型写法,类型推导完整。ref<User[]>([])能保证users里只能放User类型的数据,编译期就拦住类型错误。

从Options API迁移

已有项目从Options API迁到组合式API,不用一口气全改。Vue3支持两种API混用,可以渐进迁移。

策略上,新组件直接用<script setup>写,老组件在改动时顺手迁过来。优先迁的是那些逻辑复杂、用了多个Mixin的组件------这些组件迁完收益最大。

迁移时按功能拆分逻辑。一个Options API组件里可能有搜索、表格、表单三块逻辑,迁的时候把每块抽成独立的composable,<script setup>里只做组装。这个过程中你会发现,哪些逻辑耦合严重、哪些可以独立复用,代码结构会清晰很多。

注意this的问题。Options API里this指向组件实例,this.count、this.method()很常见。组合式API里没有this,所有数据和方法都是setup里的局部变量。如果老代码里大量用了this,迁移时得逐个替换,这也是迁移成本的主要来源。

还有一个容易忽略的点------v-model的写法变了。Vue3里v-model默认绑定的prop名从value改成了modelValue,事件从input改成了update:modelValue。支持多个v-model绑定到同一个组件,用v-model:title这样的语法。迁移时涉及自定义组件的地方要检查一遍。

迁移这事儿不用急,功能稳定优先。组合式API不是银弹,它解决的是逻辑组织和复用的问题,如果你的组件本来就简单,Options API写得也挺好,没必要为了迁而迁。

相关推荐
用户298698530141 小时前
Python 文档格式转换实战:RTF 转 PDF 与 HTML
python·html·api
用户7783366132111 小时前
前端开发别拿生产 Key 刷数据:用 MSW 给搜索接口做 Mock
前端·api
用户341852229221 小时前
你买的中转站真的是 GPT-6 吗?我写了个不问“你是什么模型”的检测工具
api
VIP_CQCRE6 天前
Coze 接入自定义模型更简单:用 Ace Data Cloud 打通 OpenAI Responses API
openai·api·ai agent·coze·acedatacloud
Patrick在香港6 天前
Claude 工具调用返回空:8 次失败里只有 1 次状态码不对,其余全带 200
爬虫·python·api·claude·香港
todoitbo6 天前
本地图库语义搜索实战:接上蓝耘元生代,让“傍晚的海边“能搜到图
人工智能·ai·api·工具实战
用户7783366132116 天前
搜索页的 URL 状态管理:可分享、可回退、可刷新
python·api
IvanLiu6 天前
我故意构造了 3 类上游失败:怎样避免 API 失败后误扣费?
api