打包工具的不同
Vue2中通常使用Webpack作为默认的打包工具;Vue3中官方推荐使用Vite作为默认的打包工具
搭配的生态系统不同
Vue2中状态管理库使用的是Vuex,Vue3中官方推荐使用Pinia,更小巧灵活
响应性系统的底层实现不同
Vue2中使用Object.definePropert来劫持 data 数据的 getter 和 setter 操作,使得 data中属性在被访问或赋值时,动态更新绑定的 template 模板。而 Object.defineProperty 必须遍历所有的预值才能劫持每一个属性
Object.defineProperty的缺点:
对于对象:Object.defineProperty只能监听对象的已有属性,无法监听对象新增属性或删除属性,因为它在对象上的属性需要被显式定义。这在实现响应式系统时可能需要额外的处理。
//解决方法1
this.$set(this.someObject,'b',2)
//解决方法2 添加的属性比较多
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
this.someObject = {name:"bwf", age:12}
//解决方法3 在初始化实例前声明所有根级响应式 property,哪怕只是一个空值:
<template>
<div class="">
<p>user:{{ user }}</p>
<button @click="onClick">add user info</button>
</div>,
</template>
<script>
export default {
name: "App",
components: {},
props: {},
data() {
return {
user: {},
};
},
methods: {
onClick() {
// 不会响应式
// this.user.name = "bwf";
// 可以响应式
this.$set(this.user, "name", "bwf")
// 可以响应式
// this.user = { name: "bwf" };
},
},
};
</script>
对于数组:通过下标直接改变数组或直接改变数组长度,视图也是无法更新的,也是因为监听不到(vue2 将被侦听的数组的变更方法进行了包裹, push/pop/shift/unshift/splice/sort/reverse)
当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
当你修改数组的长度时,例如:
vm.items.length = newLength
// 方法1
Vue.set(vm.items, indexOfItem, newValue)
// 方法2
vm.items.splice(indexOfItem, 1, newValue)
深度监听的问题:Object.defineProperty对于对象的深度监听较为繁琐,需要递归遍历对象的所有属性,并对每个属性进行设置,性能相对较低。
相比之下Vue3 Proxy,则没有这些问题
/**
* 模拟vue3 Proxy代理 实现对于对象的拦截
* target: 被代理的对象 data
* key: 操作的属性名
* value: 值
*/
const p = new Proxy(data, {
//读取属性会执行的回调
get(target, key, receiver) {
console.log('get', target, key);
return Reflect.get(target, key, receiver);
},
//修改或添加属性会执行的回调
set(target, key, value, receiver) {
console.log('set', target, key, value);
return Reflect.set(target, key, value, receiver);
},
//删除属性会执行的回调
deleteProperty(target, key) {
console.log('deleteProperty', target, key);
return delete target[key];
},
});
Vue3更好的 ts 支持
Vue3Fragments(片段)功能,Vue3允许组件返回多个根元素,而不再需要一个外层的包裹元素
选项式 api VS 组合式 api
在 vue2 中采用选项式 api ,代码过于分散。
而 vue3 新增了组合式 api ,一个功能模块代码会集中到一起,实现高内聚,低耦合
生命周期变化
beforeCreate -> setup() 开始创建组件之前,创建的是data和method
created -> setup()
beforeMount -> onBeforeMount 组件挂载到节点上之前执行的函数。
mounted -> onMounted 组件挂载完成后执行的函数
beforeUpdate -> onBeforeUpdate 组件更新之前执行的函数。
updated -> onUpdated 组件更新完成之后执行的函数。
beforeDestroy -> onBeforeUnmount 组件挂载到节点上之前执行的函数。
destroyed -> onUnmounted 组件卸载之前执行的函数。
activated -> onActivated 组件卸载完成后执行的函数
deactivated -> onDeactivated 在组件切换中老组件消失的时候执行
v-if 和 v-for的优先级
vue2同1个元素上v-for 优先于 v-if,最好不要把v-if和v-for同时用在一个元素上,这样会带来性能浪费,解决方案是使用computed,将处理后的数据进行遍历
vue3同1个元素上v-if 优先于 v-for
vue3增加了watchEffect,更加的灵活
<script setup>
import { ref, watchEffect } from 'vue'
const id = ref('1')
const pid = ref('0000')
const data = ref(null)
// pid id任何一个发生改变时都会触发
watchEffect(async () => {
console.log('watchEffect---start');
const response = await fetch(`api/${pid.value}/${id.value}`)
data.value = await response.json()
})
const changePid = () => {
pid.value = "99"
}
</script>
vue3不推荐mixins,更推荐组合式函数。
- 不清晰的数据来源:当使用了多个 mixin 时,实例上的数据属性来自哪个 mixin 变得不清晰,这使追溯实现和理解组件行为变得困难。这也是我们推荐在组合式函数中使用 ref + 解构模式的理由:让属性的来源在消费组件时一目了然。
- 命名空间冲突:多个来自不同作者的 mixin 可能会注册相同的属性名,造成命名冲突。若使用组合式函数,你可以通过在解构变量时对变量进行重命名来避免相同的键名。
// 创建组合式函数 composables/useCounter
import { ref, computed } from 'vue'
// 按照惯例,组合式函数名以“use”开头
export default function useCounter(initialValue) {
// 用于初始化计数器的值
const count = ref(initialValue)
function increment() {
count.value++
}
function decrement() {
count.value--
}
const doubleCount = computed(() => count.value * 2)
// 返回值 count doubleCount 是两个ref, ref 则可以维持这一响应性连接。
return {
count,
increment,
decrement,
doubleCount
}
}
//使用组合式函数
<template>
<div>
<p>Count: {{ counter.count }}</p>
<p>Double Count: {{ counter.doubleCount }}</p>
<button @click="counter.increment">Increment</button>
<button @click="counter.decrement">Decrement</button>
</div>
</template>
<script setup>
import { isRef } from 'vue';
import useCounter from './composables/useCounter'
const counter = useCounter(0)
// true
console.log('counter', isRef(counter.count));
</script>