前言

Vue 3 是一次从底层到上层的全面重写,理解 Vue 2 与 Vue 3 的核心区别对项目迁移和新项目选型至关重要。本篇会讲清楚:

  • Composition API vs Options API
  • 响应式系统重写(Proxy vs defineProperty)
  • 模板语法变化
  • 内置组件与 API 变更
  • 移除的特性与迁移方案

一、响应式系统重写

1.1 Vue 2:Object.defineProperty

// Vue 2 的实现方式
function defineReactive(obj, key, val) {
  Object.defineProperty(obj, key, {
    get() {
      // 依赖收集
      track(target, key)
      return val
    },
    set(newVal) {
      if (newVal === val) return
      val = newVal
      // 派发更新
      trigger(target, key)
    }
  })
}

// 问题:
// 1. 无法检测属性添加/删除(需要 Vue.set / Vue.delete)
// 2. 无法检测数组索引修改和长度修改
// 3. 需要递归遍历所有属性,初始化性能差

1.2 Vue 3:Proxy

// Vue 3 的实现方式
const handler = {
  get(target, key, receiver) {
    track(target, key)
    return Reflect.get(target, key, receiver)
  },
  set(target, key, value, receiver) {
    const result = Reflect.set(target, key, value, receiver)
    trigger(target, key)
    return result
  },
  deleteProperty(target, key) {
    const result = Reflect.deleteProperty(target, key)
    trigger(target, key)
    return result
  }
}

const state = new Proxy({ count: 0 }, handler)

// 优势:
// 1. 可以检测属性添加/删除
// 2. 可以检测数组索引修改和长度修改
// 3. 惰性代理,只在访问时才代理深层属性

1.3 对比

对比项 Vue 2 Vue 3
API Object.defineProperty Proxy
属性添加/删除 无法检测,需要 Vue.set 自动检测
数组索引修改 无法检测 自动检测
初始化性能 递归遍历所有属性 惰性代理,按需递归
兼容性 IE11 支持 IE11 不支持

二、Composition API vs Options API

2.1 Options API(Vue 2 风格)

export default {
  data() {
    return { count: 0, name: 'Alice' }
  },
  computed: {
    double() {
      return this.count * 2
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  mounted() {
    console.log('mounted')
  }
}

2.2 Composition API(Vue 3 风格)

<script setup>
import { ref, computed, onMounted } from 'vue'

const count = ref(0)
const name = ref('Alice')
const double = computed(() => count.value * 2)
const increment = () => count.value++

onMounted(() => {
  console.log('mounted')
})
</script>

2.3 核心区别

对比项 Options API Composition API
代码组织 按选项类型分组 按逻辑关注点分组
逻辑复用 Mixins(有命名冲突) Composables(来源清晰)
TypeScript 类型推导困难 完整类型推导
this 依赖 this 上下文 this,变量直接引用
可读性 简单组件直观 复杂组件更清晰

2.4 逻辑组织对比

// Options API:同一个功能的代码分散在不同选项中
export default {
  data() {
    return {
      // 用户相关
      userName: '',
      // 主题相关
      theme: 'light',
      // 分页相关
      page: 1,
      pageSize: 10
    }
  },
  methods: {
    // 用户相关方法
    fetchUser() { },
    // 主题相关方法
    toggleTheme() { },
    // 分页相关方法
    changePage() { }
  }
}

// Composition API:同一功能的代码聚合在一起
import { useUser } from './composables/useUser'
import { useTheme } from './composables/useTheme'
import { usePagination } from './composables/usePagination'

const { userName, fetchUser } = useUser()
const { theme, toggleTheme } = useTheme()
const { page, pageSize, changePage } = usePagination()

三、模板语法变化

3.1 v-model 变更

<!-- Vue 2 -->
<!-- 默认 prop: value,事件: input -->
<MyInput v-model="text" />
<!-- 等价于 -->
<MyInput :value="text" @input="text = $event" />

<!-- Vue 3 -->
<!-- 默认 prop: modelValue,事件: update:modelValue -->
<MyInput v-model="text" />
<!-- 等价于 -->
<MyInput :modelValue="text" @update:modelValue="text = $event" />

<!-- Vue 3 支持多个 v-model -->
<MyForm v-model:name="name" v-model:age="age" />

3.2 v-if 与 v-for 优先级

<!-- Vue 2:v-for 优先级更高 -->
<!-- 以下代码在 Vue 2 中可以工作 -->
<li v-for="item in list" v-if="item.active">
  {{ item.name }}
</li>

<!-- Vue 3:v-if 优先级更高 -->
<!-- 以下代码在 Vue 3 中 item 未定义! -->
<!-- 需要用 computed 先过滤 -->

3.3 移除 .native 修饰符

<!-- Vue 2:组件根元素的原生事件需要 .native -->
<MyButton @click.native="handleClick" />

<!-- Vue 3:自动透传,不再需要 .native -->
<MyButton @click="handleClick" />

四、移除的 API

4.1 事件总线 o n / on/ on/off/$once

// Vue 2:实例方法
const bus = new Vue()
bus.$on('message', handler)
bus.$emit('message', data)
bus.$off('message', handler)

// Vue 3:使用 mitt 库
import mitt from 'mitt'
const bus = mitt()
bus.on('message', handler)
bus.emit('message', data)
bus.off('message', handler)

4.2 过滤器 Filter

<!-- Vue 2 -->
{{ price | currency('$') }}

<!-- Vue 3:使用 computed 或函数替代 -->
{{ formatCurrency(price) }}

<script setup>
const formatCurrency = (value) => `$${value.toFixed(2)}`
</script>

4.3 $children

// Vue 2:访问子组件实例数组
this.$children[0].doSomething()

// Vue 3:使用 Template Refs
const childRef = ref(null)
childRef.value.doSomething()

4.4 $listeners

<!-- Vue 2:访问父组件传入的事件监听器 -->
<template>
  <button v-on="$listeners">Click</button>
</template>

<!-- Vue 3:自动透传到根元素,或用 useAttrs() -->
<script setup>
const attrs = useAttrs()
</script>

五、新增特性

5.1 Fragment(多根节点)

<!-- Vue 2:必须有单一根元素 -->
<template>
  <div>
    <header>Header</header>
    <main>Content</main>
  </div>
</template>

<!-- Vue 3:支持多根节点 -->
<template>
  <header>Header</header>
  <main>Content</main>
  <footer>Footer</footer>
</template>

5.2 Teleport

<!-- 将 DOM 传送到指定位置 -->
<Teleport to="body">
  <div class="modal">弹窗内容</div>
</Teleport>

5.3 Suspense

<!-- 异步组件加载状态管理 -->
<Suspense>
  <template #default>
    <AsyncComponent />
  </template>
  <template #fallback>
    <div>Loading...</div>
  </template>
</Suspense>

5.4 defineAsyncComponent

import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() => import('./MyComponent.vue'))

// 带配置
const AsyncComp = defineAsyncComponent({
  loader: () => import('./MyComponent.vue'),
  loadingComponent: LoadingSpinner,
  errorComponent: ErrorDisplay,
  delay: 200,
  timeout: 30000
})

六、构建与工程化

6.1 Tree-shaking

// Vue 3 支持 Tree-shaking
// 只导入使用的功能,未使用的不会打包

import { ref, computed } from 'vue'
// watch、onMounted 等未使用就不会打包

// Vue 2 不支持 Tree-shaking
// 整个 Vue 运行时都会打包

6.2 Monorepo 架构

vue/
├── packages/
│   ├── reactivity/      # @vue/reactivity
│   ├── runtime-core/    # @vue/runtime-core
│   ├── runtime-dom/     # @vue/runtime-dom
│   ├── compiler-core/   # @vue/compiler-core
│   ├── compiler-dom/    # @vue/compiler-dom
│   └── vue/             # 完整包

七、迁移要点

7.1 常见迁移踩坑

问题 Vue 2 Vue 3
属性添加 Vue.set(obj, key, val) obj[key] = val
数组更新 Vue.set(arr, index, val) arr[index] = val
事件总线 $on/$off/$emit mitt 库
过滤器 {{ val | filter }} {{ filter(val) }}
v-model value + input modelValue + update:modelValue
v-for/v-if v-for 优先 v-if 优先

7.2 Options API 与 Composition API 混用

<!-- 可以混用,但不推荐 -->
<script>
import { ref } from 'vue'

export default {
  // Options API
  data() {
    return { count: 0 }
  },
  // Composition API
  setup() {
    const name = ref('Alice')
    return { name }
  }
}
</script>

<!-- 推荐:统一使用 <script setup> -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
const name = ref('Alice')
</script>

八、面试聚焦

8.1 迁移踩坑点

  1. 属性添加/删除:Vue 2 需要 Vue.set,Vue 3 直接操作即可
  2. 事件总线:Vue 3 移除 $on/$off,需要 mitt 库替代
  3. v-model:默认 prop 从 value 改为 modelValue
  4. v-for/v-if 优先级:Vue 3 中 v-if 优先级更高

8.2 两种 API 混用问题

// Options API 和 Composition API 可以共存,但不推荐
// 原因:
// 1. 代码风格不统一
// 2. data 中的属性和 setup 中的 ref 会合并,容易混淆
// 3. 增加维护成本

九、易混淆点

  1. Options API 可以继续用:Vue 3 完全兼容 Options API,两种 API 可以在同一项目甚至同一组件中混用。
  2. <script setup> 不是新语法:它是 setup() 函数的编译时语法糖,自动暴露顶层变量。
  3. Vue 3 不支持 IE11:因为 Proxy 无法 polyfill,需要 IE11 支持请使用 Vue 2。
  4. 事件透传:Vue 3 组件的事件会自动透传到根元素,不再需要 .native 修饰符。

十、思考与练习

1. Vue 3 为什么用 Proxy 替代 Object.defineProperty?

解析:

  • Proxy 可以检测属性添加/删除
  • Proxy 可以检测数组索引修改和长度修改
  • Proxy 支持惰性代理,性能更好
  • Proxy 的缺点是不支持 IE11

2. Options API 和 Composition API 的核心区别是什么?

解析:

  • 代码组织:Options API 按选项类型分组,Composition API 按逻辑关注点分组
  • 逻辑复用:Options API 用 Mixins(有命名冲突),Composition API 用 Composables(来源清晰)
  • TypeScript:Options API 类型推导困难,Composition API 完整类型推导

3. Vue 3 移除了哪些 API?如何替代?

解析:

  • $on/$off/$once → mitt 库
  • 过滤器 → computed 或函数
  • $children → Template Refs
  • $listeners → useAttrs() 或自动透传

4. Vue 3 的 v-model 有什么变化?

解析:

  • 默认 prop 从 value 改为 modelValue
  • 事件从 input 改为 update:modelValue
  • 支持多个 v-model:v-model:namev-model:age

5. Vue 3 为什么不支持 IE11?

解析:Vue 3 使用 Proxy 实现响应式,Proxy 是 ES6 新增的原生 API,无法被 polyfill。IE11 不支持 Proxy,因此 Vue 3 无法在 IE11 上运行。


总结

  • 响应式系统:Vue 2 用 Object.defineProperty,Vue 3 用 Proxy
  • API 风格:Options API(按选项分组)vs Composition API(按逻辑分组)
  • 模板变化:v-model 默认 prop 变更、v-for/v-if 优先级变化、移除 .native
  • 移除特性 o n / on/ on/off、过滤器、 c h i l d r e n 、 children、 childrenlisteners
  • 新增特性:Fragment、Teleport、Suspense、defineAsyncComponent
  • 构建优化:Tree-shaking 支持、Monorepo 架构

更多推荐