1. API 设计风格:从 Options API 到 Composition API

Vue 2 (Options API):我们习惯于将组件的逻辑分散在 data、methods、computed、watch 等不同的选项中。这种方式对于中小型组件很直观,但在大型复杂组件中,一个功能的逻辑(比如数据、方法、计算属性)会被拆分到代码的各个角落,导致逻辑碎片化,后期维护和代码复用非常困难。

Vue 3 (Composition API):它允许我们根据逻辑功能来组织代码。所有与某个特定功能相关的响应式状态、方法、计算属性等,都可以放在同一个 setup 函数中。

Composition API可以非常轻松地将这些逻辑抽离成可复用的组合式函数 (Composables)。这彻底解决了 Vue 2 中 Mixin 模式带来的问题,比如命名冲突、数据来源不清晰等。

2. 底层响应式原理:从 Object.defineProperty 到 Proxy

Vue 2:它使用 Object.defineProperty 来劫持对象属性的 getter 和 setter。它是属性接别的劫持,这种方式存在一些天生缺陷:

  • 无法监听对象属性的新增和删除。必须使用 Vue.set 这种略显笨拙的 API 来解决。
  • 无法直接监听数组索引的变化和长度的变化。Vue 2 通过重写数组的部分方法来实现监听,但依然有局限性。

Vue 3:它改用了 ES6 的 Proxy 对象。Proxy 可以直接代理整个对象,而不是单个属性。

  • 原生支持所有监听:无论是新增/删除属性,还是直接通过索引修改数组,Proxy 都能监听到,不再需要 this.$set。
  • 性能更优:Object.defineProperty 需要在初始化时就遍历对象的所有属性进行劫持,而 Proxy 是惰性的,只有在访问属性时才会进行处理,性能开销更小。

3. 性能提升

Vue 2 整体打包,Tree-Shaking 效果差,Vue 3 源码使用 ES Module 重构,支持 Tree-Shaking,用多少打多少,体积更小。

Vue 2 基于 VNode diff,但是性能上有些限制, Vue 3 引入 静态标记(Patch Flag)区块树 (Block Tree),只更新有变化的部分,性能大幅提升。

3.1. 什么是静态标记与区块树?

区块树是告诉哪里需要更新,静态标记是告诉怎么更新(比如说,当一个 p 标签从 1 变成了 2,那么它就会像一个备注一样,告诉这个应该如何更新),

区块树就是那份“需要更新的动态节点的清单”。

再编译的时候,Vue3 会把所有的静态节点扔到一边(静态树提升),然后把所有动态节点收集起来,生成一个扁平的数组,这个只包含动态节点的就是数组,就是区块树。

当组件需要更新的时候,Vue2 必须遍历完整的全部的 DOM 节点,而 Vue3 只需要遍历这个扁平的,短更多的“区块树”即可。

其实简单来说就是这样的:

  1. 对于静态 DOM:Vue 3 通过静态树提升,在编译时就将其提升为常量。在更新时,直接跳过,完全不进行遍历和 Diff。
  2. 对于动态 DOM
    • Vue 3 会将它们收集到一个区块树 (Block Tree) 中,这是一个只包含动态节点的扁平数组。
    • 同时,会给区块树中的每一个动态节点都打上一个静态标记 (Patch Flag),明确告诉渲染器这个节点只需要更新哪一部分(文本、class、style 等)。
  1. 最终效果
    • 当需要更新时,Vue 2 必须遍历完整的虚拟 DOM 树
    • 而 Vue 3 只需要遍历短小精悍的区块树,并且在更新每个节点时,还会参考静态标记,只做最必要的操作

4. 更好的 TypeScript

Vue 3 是用 TypeScript 重写的,它的类型定义更加完善和强大,结合 Composition API,可以提供非常出色的类型推断和开发体验,这对于构建大型、健壮的应用至关重要。

5. Fragments(多根节点)

Fragment (片段):Vue 3 的组件可以有多个根节点,不再需要一个无意义的 <div> 包裹。

6. Teleport(传送门)

  • Vue2:没有,需要手写。
  • Vue3:提供 <teleport>,可将 DOM 结构渲染到指定位置(常用于弹窗)。

7. 生命周期

Vue 2 (Options API)

Vue 3 (Composition API)

说明

beforeCreate

setup()

setup 是所有 Composition API 的入口,它在组件实例创建前执行,因此完全取代了 beforeCreate。

created

setup()

setup 函数中定义的响应式数据、方法等都在 created 阶段之前完成,所以它也取代了 created。

beforeMount

onBeforeMount

组件 DOM 即将被挂载到页面之前执行。

mounted

onMounted

组件 DOM 已经被挂载到页面之后执行。

beforeUpdate

onBeforeUpdate

组件因数据变化即将更新其 DOM 树之前执行。

updated

onUpdated

组件因数据变化更新完其 DOM 树之后执行。

beforeDestroy

onBeforeUnmount

组件实例即将被卸载之前执行。名称变化更准确。

destroyed

onUnmounted

组件实例被卸载之后执行。

errorCaptured

onErrorCaptured

捕获子孙组件的错误时执行。

activated

onActivated

被 <keep-alive> 缓存的组件被激活时执行。

deactivated

onDeactivated

被 <keep-alive> 缓存的组件失活时执行。

7.1. setup 函数取代了 beforeCreatecreated

这是最关键的一个变化。在 Vue 2 中,我们在 created 钩子中初始化数据、发起网络请求等。但在 Vue 3 的 Composition API 中,这些工作都前置到了 setup 函数里。因为 setup 是组件初始化的入口,所有响应式状态、方法和计算属性都在这里创建,所以它天然地覆盖了 beforeCreate 和 created 的所有功能。

7.2. 命名和使用方式的统一

  • onX 命名规范:所有 Vue 3 的生命周期钩子都以 on 开头,比如 onMounted。这使得 API 更加见名知意,一眼就能看出这是一个注册生命周期回调的函数。
  • 需要显式导入:在 <script setup> 中,你需要从 'vue' 中显式导入这些钩子函数才能使用,比如 import { onMounted } from 'vue'。这让代码的依赖关系更加清晰。

7.3. 逻辑组合的灵活性 (最重要的优点)

在 Vue 2 的 Options API 中,如果你有一段逻辑需要在 created 中初始化,在 mounted 中绑定事件,在 beforeDestroy 中清理事件,那么这段逻辑的代码会分散在三个不同的生命周期选项里。

而在 Vue 3 中,你可以把这些都写在一起,围绕着某个具体的功能来组织代码

import { ref, onMounted, onUnmounted } from 'vue';

// 这就是一个组合式函数
export function useMouse() {
  // 1. 状态 (原本在 data 中)
  const x = ref(0);
  const y = ref(0);

  // 2. 功能逻辑 (原本在 methods 中)
  const update = (event) => {
    x.value = event.pageX;
    y.value = event.pageY;
  };

  // 3. 生命周期钩子 (原本在 mounted 和 beforeDestroy 中)
  onMounted(() => {
    window.addEventListener('mousemove', update);
  });

  onUnmounted(() => {
    window.removeEventListener('mousemove', update);
  });

  // 4. 把需要的数据和方法暴露出去
  return { x, y };
}
<template>
  <div>Mouse position is: {{ x }}, {{ y }}</div>
</template>

<script setup>
  import { useMouse } from './useMouse.js'; // 1. 导入

  const { x, y } = useMouse(); // 2. 调用,并解构出需要的数据
</script>
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐