Vue2与Vue3组件开发全维度对比实战指南

Vue2 与 Vue3 组件开发全维度对比实战指南

一、组件基础架构对比

1.1 组件定义方式演进

Vue2 Options API 详解

在 Vue2 中,组件主要通过 Options API 来定义。Options API 将组件的不同方面,如数据、方法、生命周期钩子等,分开定义在一个对象中。这种方式对于初学者来说,易于理解和上手。

以一个简单的计数器组件为例:




在这个例子中,data 函数返回一个对象,其中包含了组件的响应式数据 countmethods 对象中定义了一个方法 increment,用于增加 count 的值。当用户点击按钮时,increment 方法会被调用,从而更新 count 的值,并自动更新模板中的显示。

Vue3 Composition API 详解

Vue3 引入了 Composition API,它提供了一种更灵活、更具逻辑性的组件定义方式。Composition API 允许开发者根据逻辑相关性组织代码,而不是像 Options API 那样按照选项类型组织。

同样以计数器组件为例:




在这个例子中,我们使用 ref 函数创建了一个响应式的 count 变量。ref 函数返回一个对象,其值存储在 .value 属性中。increment 方法直接定义在 setup 函数中,用于更新 count 的值。

两种定义方式的优缺点对比
  • Options API 的优点
    • 易于学习和理解,对于初学者来说,清晰地将不同功能划分到不同选项中,降低了学习成本。
    • 代码结构清晰,每个选项的作用明确,便于维护和查找。
  • Options API 的缺点
    • 当组件变得复杂时,代码会变得冗长,同一个逻辑关注点的代码可能分散在不同的选项中,导致难以理解和维护。
    • 代码复用性较差,难以提取和复用逻辑。
  • Composition API 的优点
    • 逻辑复用性强,可以将相关的逻辑封装成函数,在不同的组件中复用。
    • 代码组织更灵活,可以根据逻辑相关性将代码组织在一起,提高了代码的可读性和可维护性。
  • Composition API 的缺点
    • 学习曲线较陡,对于初学者来说,理解和掌握 Composition API 需要一定的时间和精力。
    • 代码结构不够直观,需要开发者自己组织代码结构。

1.2 响应式系统底层差异

Vue2 的响应式系统

Vue2 的响应式系统基于 Object.defineProperty() 方法实现。当一个 Vue 实例创建时,Vue 会遍历 data 选项中的所有属性,使用 Object.defineProperty() 将这些属性转换为 getter/setter。这样,当这些属性的值发生变化时,Vue 会自动更新与之绑定的 DOM 元素。

以下是一个简单的示例:

const obj = {};
let value = 0;
Object.defineProperty(obj, 'count', {
  get() {
    return value;
  },
  set(newValue) {
    value = newValue;
    console.log('值已更新');
  }
});

obj.count = 1; // 输出: 值已更新

在这个示例中,我们使用 Object.defineProperty()obj 对象的 count 属性定义了 gettersetter。当 count 属性的值发生变化时,setter 方法会被调用,从而触发相应的更新操作。

Vue2 响应式系统的局限性
  • 数组响应式问题:Vue2 对数组的某些方法进行了重写,如 pushpopsplice 等,以确保这些操作能够触发响应式更新。但对于通过索引直接修改数组元素或修改数组长度的操作,Vue2 无法检测到变化。
const arr = [1, 2, 3];
Vue.set(arr, 1, 4); // 使用 Vue.set 方法才能触发响应式更新
  • 深度监听问题:Vue2 默认是浅监听,对于嵌套对象的属性变化,需要手动开启深度监听,否则无法检测到变化。
const obj = {
  nested: {
    value: 1
  }
};
Vue.set(obj.nested, 'value', 2); // 需要手动使用 Vue.set 才能触发响应式更新
  • 性能问题:由于 Object.defineProperty() 是在对象属性创建时就进行劫持,对于大型对象,初始化时会有一定的性能开销。
Vue3 的响应式系统

Vue3 的响应式系统基于 Proxy 对象实现。Proxy 是 ES6 引入的一个新特性,它可以拦截对象的各种操作,如属性访问、属性赋值、函数调用等。Vue3 使用 Proxy 来创建响应式对象,当对象的属性发生变化时,Proxy 会拦截这些操作,并触发相应的更新操作。

以下是一个简单的示例:

const obj = {
  count: 0
};
const proxyObj = new Proxy(obj, {
  get(target, key) {
    return target[key];
  },
  set(target, key, newValue) {
    target[key] = newValue;
    console.log('值已更新');
    return true;
  }
});

proxyObj.count = 1; // 输出: 值已更新

在这个示例中,我们使用 Proxyobj 对象创建了一个代理对象 proxyObj。当访问或修改 proxyObj 的属性时,Proxygetset 方法会被调用,从而实现响应式更新。

Vue3 响应式系统的优势
  • 数组响应式增强:Vue3 对数组的所有操作都能自动检测到变化,无需像 Vue2 那样重写数组方法。
const arr = reactive([1, 2, 3]);
arr[1] = 4; // 自动触发响应式更新
  • 深度监听默认开启:Vue3 默认对嵌套对象进行深度监听,无需手动开启。
const obj = reactive({
  nested: {
    value: 1
  }
});
obj.nested.value = 2; // 自动触发响应式更新
  • 性能优化Proxy 是在对象访问时进行拦截,相比 Object.defineProperty(),初始化时的性能开销更小。
响应式系统差异对组件开发的影响
  • 代码简洁性:Vue3 的响应式系统使得代码更加简洁,无需处理 Vue2 中数组和嵌套对象的响应式问题。
  • 性能提升:在处理大型对象和频繁更新的场景下,Vue3 的响应式系统性能更优。
  • 学习成本:Vue3 的响应式系统引入了新的概念和 API,如 reactiveref 等,对于初学者来说,学习成本会有所增加。

️ 二、生命周期变化详解

2.1 生命周期对应关系

Vue2 生命周期钩子

Vue2 有多个生命周期钩子,用于在组件的不同阶段执行特定的代码。常见的生命周期钩子包括:

  • beforeCreate:在实例初始化之后,数据观测 dataevent/watcher 事件配置之前被调用。
  • created:实例已经创建完成之后被调用。在这一步,实例已经完成了数据观测 datapropertymethod 的计算、watch/event 事件回调。然而,挂载阶段还没有开始,$el 属性目前不可用。
  • beforeMount:在挂载开始之前被调用,相关的 render 函数首次被调用。
  • mountedel 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。
  • beforeUpdate:在数据更新之前被调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
  • updated:在由于数据更改导致的虚拟 DOM 重新渲染和打补丁之后调用。调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watch 取而代之。
  • beforeDestroy:在实例销毁之前调用。在这一步,实例仍然完全可用。
  • destroyed:在实例销毁之后调用。调用后,所有的事件监听器和子实例都已经被销毁。
Vue3 生命周期钩子

Vue3 对生命周期钩子进行了一些调整和重命名,同时引入了组合式 API 的使用方式。对应的生命周期钩子如下:

  • setup:在创建组件实例,初始化 propscontext 之后,在 beforeCreatecreated 之前调用。setup 函数是组合式 API 的入口点,用于定义组件的响应式数据、方法和生命周期钩子。
  • onBeforeMount:相当于 Vue2 的 beforeMount,在挂载开始之前被调用。
  • onMounted:相当于 Vue2 的 mounted,在挂载完成之后被调用。
  • onBeforeUpdate:相当于 Vue2 的 beforeUpdate,在数据更新之前被调用。
  • onUpdated:相当于 Vue2 的 updated,在数据更新之后被调用。
  • onBeforeUnmount:相当于 Vue2 的 beforeDestroy,在实例销毁之前调用。
  • onUnmounted:相当于 Vue2 的 destroyed,在实例销毁之后调用。
生命周期对应关系图
Vue2
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
beforeDestroy
destroyed
setup
onBeforeMount
onMounted
onBeforeUpdate
onUpdated
onBeforeUnmount
onUnmounted

2.2 组合式 API 生命周期使用

使用组合式 API 编写生命周期钩子

在 Vue3 中,使用组合式 API 编写生命周期钩子非常简单。只需要在 setup 函数中导入相应的生命周期钩子函数,并在合适的位置调用即可。

以下是一个使用组合式 API 编写的组件示例:




在这个示例中,我们在 setup 函数中导入了 onMountedonUpdatedonBeforeUnmount 生命周期钩子函数,并在相应的回调函数中执行了一些操作。

生命周期钩子在组合式 API 中的优势
  • 逻辑复用:可以将生命周期钩子相关的逻辑封装成函数,在不同的组件中复用。
import { onMounted } from 'vue';

const useMountedEffect = (callback) => {
  onMounted(callback);
};

export { useMountedEffect };

在组件中使用:




  • 代码组织:可以根据逻辑相关性将生命周期钩子和其他逻辑代码组织在一起,提高代码的可读性和可维护性。
注意事项
  • setup 函数中,this 不再指向组件实例,因此不能使用 this 来访问组件的属性和方法。
  • 组合式 API 的生命周期钩子函数只能在 setup 函数中使用,不能在组件的其他选项中使用。

️ 三、模板语法深度对比

️ 3.1 v-model 双向绑定升级

Vue2 的 v-model 实现

在 Vue2 中,v-model 是一个语法糖,它实际上是 :value@input 的组合。对于不同的表单元素,v-model 的实现方式略有不同。

以下是一个使用 v-model 绑定输入框的示例:




在这个示例中,v-model="message" 实际上等价于 :value="message" @input="message = $event.target.value"

Vue2 v-model 的局限性
  • 只能绑定一个属性:Vue2 的 v-model 只能绑定一个属性,对于需要绑定多个属性的情况,需要手动处理。
  • 自定义组件的 v-model 实现复杂:在自定义组件中使用 v-model,需要在组件中定义 propevent,并手动处理数据的双向绑定。
Vue3 的 v-model 升级

Vue3 对 v-model 进行了升级,提供了更强大和灵活的功能。

  • v-model 支持:Vue3 允许在一个组件上使用多个 v-model 绑定。



ChildComponent.vue 中: