Vue3,那个从选项式到组合式的蜕变

作者:互联网

2026-03-20

Javascript教程

Vue3,那个从选项式到组合式的蜕变

我们组有个前端老哥,用了五年 Vue2。有天他升级到 Vue3,盯着代码看了半小时,然后说了句:"这不是 Vue 了,这是 JavaScript。"

他说得没错。Vue3 就像一个人从被管家安排得井井有条的生活,突然有了自己选择的自由。

那个管家叫"选项式"

Vue2 的时候,咱们写组件就像填表格:

export default {
  data() {
    return { count: 0, user: null }
  },
  methods: {
    increment() { this.count++ },
    fetchUser() { /* 请求逻辑 */ }
  },
  computed: {
    doubled() { return this.count * 2 }
  },
  watch: {
    count(newVal) { console.log(newVal) }
  }
}

data 放数据,methods 放方法,computed 放计算属性,watch 放。每个东西都有自己的格子,管家(Vue)帮你整理得妥妥帖帖。

问题是,当逻辑复杂了,这些格子就散开了。比如一个"用户登录"的功能,验证逻辑在 methods 里,状态在 data 里,错误提示在 computed 里,在 watch 里。你得在五个地方跳来跳去,就像在一个迷宫里找东西。

更糟的是,如果两个组件都需要"计数器"逻辑,你得复制粘贴整个 datamethodscomputed。代码重复,维护困难。

后来来了个叫"组合式"的东西

Vue3 引入了 Composition API,让你可以把相关逻辑写在一起。比如提取一个 useCounter 的函数:

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

function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  const doubled = computed(() => count.value * 2)
  const increment = () => count.value++
  const decrement = () => count.value--

  watch(count, (newVal) => {
    console.log(`计数变化: ${newVal}`)
  })

  return { count, doubled, increment, decrement }
}

然后在任何组件里复用它:

import { useCounter } from './useCounter'

export default {
  setup() {
    const { count, doubled, increment, decrement } = useCounter(10)
    return { count, doubled, increment, decrement }
  }
}

这就像把"计数逻辑"打包成一个独立的工具,需要的时候拿出来用。两个组件都需要计数?各自调用一次 useCounter,互不影响。

响应式系统,从"魔法"变成了"透明"

Vue2 用 Object.defineProperty 做响应式,有些坑:

// Vue2 的坑
data() {
  return { user: { name: 'Tom' } }
},
methods: {
  addField() {
    this.user.age = 30  // 不会触发更新!
    this.$set(this.user, 'age', 30)  // 得这样才行
  }
}

Vue3 用 Proxy,直接了当。有两种方式:

reactive 处理对象

import { reactive } from 'vue'

const user = reactive({ name: 'Tom' })
user.age = 30  // 直接就能用,没坑
user.email = 'tom@example.com'  // 新增字段也行

ref 包装任何类型

import { ref } from 'vue'

const count = ref(0)
const user = ref({ name: 'Tom' })
const list = ref([1, 2, 3])

count.value++  // 改 value 属性
user.value.age = 30  // 对象也得改 value
list.value.push(4)  // 数组也一样

窗外下着雨,我问那个老哥为什么要改成 value。他说:"这样你一眼就知道这是响应式的,不用猜。而且 ref 可以包装任何东西,基础类型、对象、数组都行。"

响应式数据,从"watch"到"watchEffect"

Vue2 的 watch 得手动指定的属性:

watch: {
  user: {
    handler(newVal) {
      console.log('用户变化:', newVal)
    },
    deep: true  // 深度,性能差
  }
}

Vue3 的 watch 更灵活:

import { watch, ref } from 'vue'

const user = ref({ name: 'Tom', age: 30 })

// 整个对象
watch(user, (newVal) => {
  console.log('用户变化:', newVal)
}, { deep: true })

// 对象的某个属性
watch(() => user.value.name, (newVal) => {
  console.log('名字变化:', newVal)
})

// 多个数据源
const count = ref(0)
watch([user, count], ([newUser, newCount]) => {
  console.log('用户或计数变化')
})

还有个更方便的 watchEffect,自动追踪依赖:

import { watchEffect, ref } from 'vue'

const user = ref({ name: 'Tom' })
const count = ref(0)

watchEffect(() => {
  // 这里用到了 user 和 count,watchEffect 会自动它们
  console.log(`${user.value.name} 的计数: ${count.value}`)
})

// 改任何一个,都会触发
user.value.name = 'Jerry'  // 触发
count.value++  // 也触发

组件通信,从"props/emit"到"provide/inject"

Vue2 的组件通信,层级深了就得一层层传:

// 爷爷组件
user="user" @update="handleUpdate" />

// 父组件
user="user" @update="handleUpdate" />

// 孙子组件
user="user" @update="handleUpdate" />

Vue3 用 provide/inject 可以跨越多层:

// 爷爷组件
import { provide, ref } from 'vue'

export default {
  setup() {
    const user = ref({ name: 'Tom' })
    const updateUser = (newUser) => {
      user.value = newUser
    }

    // 提供给所有后代组件
    provide('user', user)
    provide('updateUser', updateUser)
  }
}

// 孙子组件,直接注入,不用经过父组件
import { inject } from 'vue'

export default {
  setup() {
    const user = inject('user')
    const updateUser = inject('updateUser')

    return { user, updateUser }
  }
}

就像在公司里,爷爷直接给孙子发通知,不用经过父亲转达。

性能优化,从"全量更新"到"精准打击"

Vue2 的虚拟 DOM 更新,有时候会更新一些不必要的节点。Vue3 做了几个优化:

静态提升:不变的东西,编译时就提出来,不用每次都重新创建。

<template>
  <div>
    <p>{{ msg }}p>
    <p>不变的文本p>
    <button @click="increment">+1button>
  div>
template>

编译后,"不变的文本"这个节点只创建一次,不会在每次更新时重新创建。

事件缓存:事件处理函数也缓存起来。

<template>
  
  <button @click="increment">+1button>
template>

Diff 算法优化:标记了哪些节点是动态的,哪些是静态的,更新时直接跳过静态部分。

结果就是,Vue3 的性能比 Vue2 快了 1.3 到 2 倍。

那个