技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTVUE

盘点 Vue 3 与 Vue 2 的区别

所属馆藏
Vue
文件格式
Markdown
原始路径
Vue/92-盘点 Vue3 与 Vue2 的区别
本文目录15 个章节
  1. 一、先明确版本和迁移背景
  2. 二、生命周期变化
  3. 三、应用实例与全局 API
  4. 四、多根节点与 Fragment
  5. 五、Composition API
  6. 六、异步组件与 Suspense
  7. 七、Teleport
  8. 八、响应式原理差异
  9. 九、虚拟 DOM 与 Vue 3 编译优化
  10. 十、事件和组件通信的变化
  11. 十一、v-model、过滤器和其他迁移点
  12. 十二、Tree-shaking 与 TypeScript
  13. 十三、Options API 和 Composition API 如何选择
  14. 总结
  15. 参考资料

盘点 Vue 3 与 Vue 2 的区别

原文从生命周期、多根节点、Composition API、Suspense、Teleport、响应式、虚拟 DOM、事件缓存、Diff、Tree-shaking 和 TypeScript 等方面比较 Vue 2 与 Vue 3。本文保留这些主题,修复损坏的 SFC/渲染函数示例,并补充 Vue 2.7、Vue 3.5 和 Nuxt 4 项目中的实际差异。

一、先明确版本和迁移背景

  • Vue 2.7 是 Vue 2 的最终次版本,提供了部分 Composition API 能力,但 Vue 2 已结束官方维护;
  • Vue 3 是当前主版本,项目通常使用 Vue 3.5.x;
  • Vue 2 的 new Vue()Vue.use()Vue.set() 等写法属于历史 API;
  • Vue 3 使用 createApp(),生态库通常使用 createRouter()、Pinia 和 Composition API;
  • Vue 3 仍然支持 Options API,Composition API 不是强制替代品;
  • Nuxt 4 基于 Vue 3,页面路由、SSR 和数据获取还需要遵循 Nuxt 的约定。

“Vue 3 完全重写所以所有代码都不能用”以及“Vue 3 会淘汰 Options API”都不准确。迁移时应按 API、构建方式和生态依赖逐项检查。

二、生命周期变化

1. 生命周期对照

Vue 2 Options APIVue 3 Options APIVue 3 Composition API
beforeCreatebeforeCreate通常由 setup() 开始前后流程覆盖
createdcreated通常在 setup() 中执行初始化逻辑
beforeMountbeforeMountonBeforeMount
mountedmountedonMounted
beforeUpdatebeforeUpdateonBeforeUpdate
updatedupdatedonUpdated
beforeDestroybeforeUnmountonBeforeUnmount
destroyedunmountedonUnmounted
activatedactivatedonActivated
deactivateddeactivatedonDeactivated

Vue 3 Composition API 的生命周期钩子需要从 vue 导入,并且要在 setup() 执行期间注册:

<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue'

function onResize() {
  console.log(window.innerWidth)
}

onMounted(() => {
  window.addEventListener('resize', onResize)
})

onUnmounted(() => {
  window.removeEventListener('resize', onResize)
})
</script>

同一个钩子可以注册多个回调,它们会按注册顺序执行;不会因为后一次调用就覆盖前一次。

setup() 可以理解为组合式逻辑的入口,而不是简单地把 Vue 2 的 beforeCreatecreated 两个钩子机械拼接在一起。需要访问真实 DOM 的逻辑仍应放在 onMounted 之后;SSR 中 onMounted 不会在服务端执行。

三、应用实例与全局 API

Vue 2 常见写法:

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false
Vue.prototype.$http = http

new Vue({
  render: h => h(App),
}).$mount('#app')

Vue 3 改为创建应用实例:

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

app.config.globalProperties.$http = http
app.provide('http', http)
app.mount('#app')

主要变化:

  • Vue 2 的全局配置、插件和原型容易影响同页面的所有根实例;
  • Vue 3 的 app 实例拥有自己的配置、插件和 provides;
  • Vue 2 Vue.use(plugin) 对应 Vue 3 app.use(plugin)
  • Vue 2 Vue.component() 对应 Vue 3 app.component()
  • Vue 2 Vue.nextTick() 在 Vue 3 使用从 vue 导入的 nextTick()
  • Vue 3 不再有 Vue.setVue.delete,Proxy 可以处理响应式对象的新增/删除。

四、多根节点与 Fragment

Vue 2 的组件模板需要一个根元素:

<!-- Vue 2:通常会报多根节点错误 -->
<template>
  <header />
  <main />
  <footer />
</template>

Vue 3 支持 Fragment,可以有多个根节点:

<template>
  <header />
  <main />
  <footer />
</template>

但多根组件的属性继承需要显式处理。Vue 3 无法猜测 $attrs 应该落到哪一个根节点:

<script setup>
defineOptions({ inheritAttrs: false })

defineProps<{ title: string }>()
</script>

<template>
  <header>标题</header>
  <main v-bind="$attrs" />
</template>

Vue 3.3+ 可以使用 defineOptions;不使用 <script setup> 时可以写 inheritAttrs: false。Fragment 不是把所有属性自动平均分配给多个根节点。

五、Composition API

Composition API 是一组导入式 API,包含 refreactivecomputedwatch、生命周期钩子和 provide/inject 等。

Vue 2.7 已内置部分 Composition API,旧版 Vue 2 可使用官方维护过的 @vue/composition-api 插件;Vue 3 则原生支持,并常与 <script setup> 一起使用。

Options API 示例

<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
  data() {
    return { count: 0 }
  },
  methods: {
    increment() {
      this.count++
    },
  },
  mounted() {
    console.log(this.count)
  },
})
</script>

<template>
  <button @click="increment">count is: {{ count }}</button>
</template>

Composition API 示例

<script setup lang="ts">
import { onMounted, ref } from 'vue'

const count = ref(0)

function increment() {
  count.value++
}

onMounted(() => {
  console.log(count.value)
})
</script>

<template>
  <button @click="increment">count is: {{ count }}</button>
</template>

Composition API 的优势主要是:

  • 按业务逻辑组织代码,而不是把同一逻辑拆散到 datamethodswatch 和生命周期中;
  • 通过 composable 复用有状态逻辑,减少 mixin 的命名冲突和隐式依赖;
  • 普通变量和函数更利于 TypeScript 推导;
  • 在大型组件和复杂逻辑中更容易抽取、测试和组合。

Options API 仍然是 Vue 3 的正式 API,简单组件或团队更熟悉 Options API 时继续使用完全合理。两种 API 也可以在同一个组件中组合,但应明确状态归属,避免让同一逻辑在两套风格之间来回跳转。

六、异步组件与 Suspense

1. 异步组件

Vue 2 常见异步组件工厂写法:

Vue.component('async-component', function (resolve) {
  require(['./MyComponent.vue'], resolve)
})

Vue 3 推荐 defineAsyncComponent 和动态 import()

import { defineAsyncComponent } from 'vue'

const AsyncPanel = defineAsyncComponent({
  loader: () => import('./Panel.vue'),
  loadingComponent: LoadingPanel,
  errorComponent: ErrorPanel,
  delay: 200,
  timeout: 10000,
})

2. Suspense 的边界

Vue 3 提供内置 <Suspense>,可以等待异步组件或包含顶层 awaitasync setup(),并在 pending 阶段显示 fallback:

<template>
  <Suspense>
    <template #default>
      <AsyncPanel />
    </template>

    <template #fallback>
      <div>Loading...</div>
    </template>
  </Suspense>
</template>

需要注意:

  • 当前 Vue 文档仍将 Suspense 标为实验性 API,升级时应关注版本说明;
  • 它主要等待异步依赖,不是任意“计算量很大所以自动显示 loading”;
  • 请求错误处理、重试和业务空状态仍应由组件或数据层负责;
  • 在 Nuxt 4 中还要结合 Nuxt 的 SSR、useAsyncData/useFetch 和页面 loading 方案判断是否需要手写 Suspense。

七、Teleport

Vue 3 的 <Teleport> 可以把一部分 DOM 渲染到组件逻辑之外的目标节点,常用于 Dialog、Popover、全局通知:

<template>
  <button @click="visible = true">显示弹窗</button>

  <Teleport to="body">
    <div v-if="visible" class="dialog">
      我在 body 下渲染,但仍属于当前组件的逻辑树
    </div>
  </Teleport>
</template>

<script setup>
import { ref } from 'vue'

const visible = ref(false)
</script>

Teleport 改变的是 DOM 挂载位置,不会改变组件的逻辑父子关系:props、事件、provide/inject 仍按组件树工作。目标元素应存在或使用适合当前版本的延迟目标能力;SSR/水合场景还要保证服务端和客户端结构一致。

Vue 2 没有内置 Teleport,历史项目可使用 portal-vue 等方案,但迁移时要检查库的 Vue 版本和 SSR 支持。

八、响应式原理差异

1. Vue 2:getter/setter

Vue 2 初始化时遍历对象已有属性,用 Object.defineProperty 安装 getter/setter:

const state = {}
let count = 0

Object.defineProperty(state, 'count', {
  enumerable: true,
  configurable: true,
  get() {
    return count
  },
  set(value) {
    count = value
  },
})

限制包括:

  • 初始化后新增对象属性无法被自动转换;
  • 删除属性无法被普通 setter 感知;
  • 直接设置数组下标和 length 有限制;
  • 需要改写 pushsplice 等七个数组变异方法;
  • Vue 2 使用 Vue.set/vm.$setVue.delete/vm.$delete 处理部分场景。

2. Vue 3:Proxy 与 ref getter/setter

import { reactive, ref } from 'vue'

const state = reactive({ count: 0 })
state.newProperty = '可以直接被追踪'
delete state.newProperty

const count = ref(0)
count.value++

Vue 3 的 reactive 通过 Proxy 拦截对象/数组的 get、set、delete、has 和迭代;Map/Set 由专门的 collection handlers 处理。ref 仍然通过 .value 的访问器属性追踪依赖,所以“Vue 3 完全不使用 getter/setter”也不准确。

reactive() 默认深度转换嵌套对象,shallowReactive/shallowRef 可用于特殊性能或外部状态场景。解构 reactive 对象属性时要注意响应式连接丢失,可使用 toRef/toRefs

九、虚拟 DOM 与 Vue 3 编译优化

原文中的 Vue 3 渲染函数被抓取成了缺少引号、导入和逗号的无效代码。概念上,下面的模板:

<div id="app">
  <div>静态内容</div>
  <p>{{ age }}</p>
</div>

可能被编译为类似这样的代码:

const hoisted = createElementVNode('div', null, '静态内容', -1)

function render(_ctx, _cache) {
  return openBlock(), createElementBlock('div', { id: 'app' }, [
    hoisted,
    createElementVNode(
      'p',
      null,
      toDisplayString(_ctx.age),
      1, // TEXT patch flag
    ),
  ])
}

真实生成代码会根据编译模式、插件、scopeId、SSR 和模板结构不同而变化,建议使用当前 Template Explorer 查看。

Vue 3 的重要运行时提示包括:

  • Patch flags:标记动态文本、class、style、props、事件或 Fragment 类型;
  • 静态提升:静态 vnode/属性对象只创建一次并复用;
  • Block tree:记录动态后代,更新时跳过大量静态节点;
  • Keyed diff:对有 key 的子节点使用 key 映射和移动优化;
  • 事件处理器缓存:某些编译场景会通过 _cache 避免重复创建内联 handler,但不能概括为所有事件都变成静态节点。

Patch flag 判断使用位运算:

if (vnode.patchFlag & PatchFlags.TEXT) {
  // 只处理动态文本
}

不是原文中错误的 patchFlag && PatchFlags.KEYED_FRAGMENT

十、事件和组件通信的变化

Vue 2 组件通信常见 $emit$on$off$once$listeners$children 和事件总线。Vue 3 仍支持组件 $emit/emits,但移除了实例事件 API $on$off$once,也移除了 $listeners$children

Vue 3 推荐:

<!-- 子组件 -->
<script setup lang="ts">
const emit = defineEmits<{
  change: [value: string]
}>()

emit('change', 'new value')
</script>

跨层通信可以使用 provide/inject、Pinia 或明确的事件/状态服务,不建议把全局事件总线当作所有问题的默认答案。

十一、v-model、过滤器和其他迁移点

1. 组件 v-model

Vue 2 默认使用 value prop + input 事件:

<custom-input v-model="searchText" />

Vue 3 默认使用 modelValue prop + update:modelValue 事件,并支持参数:

<CustomInput v-model="searchText" />
<CustomInput v-model:title="title" />

组件可以使用 defineModel()(Vue 3.4+)简化双向绑定,也可以显式声明 propsemits。Vue 2 的 .sync 在 Vue 3 中改为参数化 v-model,但迁移要检查第三方组件 API。

2. 过滤器

Vue 2 的模板过滤器在 Vue 3 中被移除,推荐使用普通函数、computed 或方法:

<template>
  <span>{{ formatDate(createdAt) }}</span>
</template>

3. 属性与事件 fallthrough

Vue 3 用 $attrs 同时承载未声明的属性和监听器 fallthrough;组件应通过 inheritAttrs: falsev-bind="$attrs" 明确控制多根或特殊组件的属性落点。emits 声明还有助于避免事件被误当作根元素原生监听器。

十二、Tree-shaking 与 TypeScript

1. Tree-shaking

Vue 2 常见的是全局构造函数和不同构建版本:

import Vue from 'vue'

Vue.nextTick(() => {})

Vue 3 使用 ESM 命名导出和应用实例:

import { nextTick } from 'vue'

await nextTick()

现代 bundler 可以在满足条件时移除没有引用的 ESM 代码,但最终体积取决于构建工具、压缩器、编译模式、依赖和应用代码。不要固定宣称 Vue 3 永远只有某个精确 KB 数;也不要把所有内部 API 都当作公共命名导出。

2. TypeScript

Vue 3 代码库使用 TypeScript,Composition API 主要由普通变量、函数和泛型组成,类型推导通常更直接:

<script setup lang="ts">
import { ref } from 'vue'

type User = {
  id: number
  name: string
}

const user = ref<User | null>(null)
</script>

Vue 2.7 的类型支持已经比早期版本更好,但 Options API、mixin、插件注入和 class API 仍可能让推导复杂。Vue 3 官方不再推荐以 class API 作为主要组件写法,Composition API 和 <script setup> 通常更适合新项目。

十三、Options API 和 Composition API 如何选择

场景建议
小型组件、团队熟悉 Vue 2 写法Options API 足够清晰
复杂组件、多种业务逻辑交叉Composition API 更容易按逻辑组织
需要抽取复用逻辑composable
迁移中的旧项目保留 Options API,局部引入 Composition API
新的 Vue 3/Nuxt 4 项目优先考虑 <script setup>,但不是硬性要求

Composition API 不是函数式编程,也不是“每次更新都重新调用整个组件函数”的 React Hooks 模型;setup()/<script setup> 通常在组件实例创建时执行一次,响应式 effect 负责后续更新。

总结

  1. Vue 3 变化很多,但 Options API 仍然受支持;
  2. Vue 2 的 beforeDestroy/destroyed 对应 Vue 3 的 beforeUnmount/unmounted
  3. Vue 3 支持 Fragment、Teleport、Suspense,Suspense 目前仍应视为实验性能力;
  4. Vue 2 使用 Object.defineProperty,Vue 3 的 reactive 使用 Proxy,ref 仍使用 getter/setter;
  5. Vue 3 的性能优化来自编译器提示、patch flags、静态提升、block tree、调度器和 renderer 协同;
  6. Vue 3 移除了实例事件总线 API、过滤器、$listeners$children
  7. Vue 3 使用 createApp、命名导出、emits、参数化 v-model 和更好的 TypeScript 支持;
  8. 迁移不能只替换生命周期名称,应结合组件通信、插件、路由、状态管理、构建和 SSR 一起验证。

参考资料

原文作者:cv搬砖工。原文生命周期、Fragment、Composition API、Suspense、Teleport、响应式、VDOM、事件缓存、Diff、Tree-shaking 和 TypeScript 的章节予以保留;损坏的模板/渲染函数、Vue 3 性能倍数的绝对结论、事件缓存的过度概括、patchFlag 位运算错误以及 Vue 3 会淘汰 Options API 等说法已修正。

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS