vue3在组合API中使用生命周期钩子时需要先引入,而vue2在选项API中可以直接调用钩子
setup是围绕beforeCreate和created生命周期钩子运行的,所以不需要显式地去定义
vue2中,在模板中如果使用多个根节点时会报错,而vue3 支持多个根节点
vue2 是选项式API(Options API),一个逻辑灰散乱在文件的不同位置(data、props、computed、watch、生命周期等),导致代码地可读性变差。
vue3组合式API(Composition PI)则很好地解决了这个问题,可将同一逻辑的内容写到一起,增强了代码的可读性、内聚性,其还提供了较为完美的逻辑复用性方案。
Options API 碎片化使得理解和维护复杂组件变得困难, 选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块
Compositon API 将某个逻辑关注点相关的代码全都放在一个函数里,这样当需要修改一个功能时,就不再需要在文件中跳来跳去
function useCount() {
let count = ref(10);
let double = computed(() => {
return count.value * 2;
});
const handleConut = () => {
count.value = count.value * 2;
};
console.log(count);
return {
count,
double,
handleConut,
};
}
组件上使用count
export default defineComponent({
setup() {
const { count, double, handleConut } = useCount();
return {
count,
double,
handleConut
}
},
});
在vue2中,我们是通过mixin实现功能混合,如果多个mixin混合,会存在两个非常明显的问题:命名冲突和数据来源不清晰
而通过composition这种形式,可以将一些复用的代码抽离出来作为一个函数,只要的使用的地方直接进行调用即可
在vue2中,通过mixin实现功能混合的缺陷
1.命名冲突:如果多个 Mixin 中包含了相同的选项,会导致命名冲突,这可能会导致不可预期的行为。
2.隐式依赖:当多个 Mixin 依赖于相同的属性或方法时,可能会出现隐式的依赖关系,使得代码难以维护和理解。
3.耦合性增强:Mixin 可能会导致组件与多个不同的代码片段紧密耦合,这会增加代码的复杂度和维护难度。
4.全局污染:如果一个 Mixin 修改了全局对象或其他共享资源,可能会影响到整个应用程序。
5.不可追踪性:当一个组件使用了多个 Mixin 时,很难追踪每一个 Mixin 对组件行为的影响。
6.调试困难:当出现问题时,定位问题所在可能会比较困难,特别是当多个 Mixin 交叉影响时。
7.组件复用性:过度使用 Mixin 可能会导致组件的复用性降低,因为组件的功能散布在多个 Mixin 中。
8.组件难以理解:Mixin 可能会导致组件的行为变得复杂,特别是当多个 Mixin 互相影响时,会使得组件的行为难以理解。
因此,在使用 Vue 2 中的 Mixin 时,需要谨慎考虑,尽量避免上述问题的出现。同时,也可以考虑使用其他方式,如使用组件复合、插槽(slot)、高阶组件(Higher Order Component)等来达到类似的功能复用效果,但更加灵活和可控。vue3 的组合式API很好解决了这种问题
在 Vue 3 中,提供了 Composition API(组合式 API)作为一种新的组件组织方式,它可以更好地解决了在 Vue 2 中使用 Mixin 可能出现的一些问题:
命名冲突问题减少:Composition API 使用函数组合的方式,可以将相关的逻辑组织在一个函数内部,减少了命名冲突的可能性。
1.明确的依赖关系:Composition API 允许你在组件内部明确地声明你的依赖关系,这使得组件的依赖关系更加清晰。
2.提供了更强大的逻辑封装:通过 Composition API,可以更灵活地组合和封装逻辑,使得代码更易于理解和维护。
3.模块化和可组合性:Composition API 鼓励将逻辑以函数的形式封装,这使得逻辑可以更容易地进行模块化和复用。
4.代码的可追踪性:由于逻辑被封装在函数内部,所以在组件内部可以更容易地追踪逻辑的来源和影响。
5.更好的代码隔离:Composition API 允许你将逻辑按功能组织在不同的函数中,从而实现更好的代码隔离,减少了耦合性。
总的来说,Composition API 提供了一种更灵活、可组合、可控的方式来组织组件的逻辑,相比于 Vue 2 的 Mixin,可以更好地解决了命名冲突、依赖关系不清晰等问题,使得组件的代码更容易理解、维护和复用。
Vue3提供Suspense组件,允许程序在等待异步组件加载完成前渲染兜底的内容,如loading,使用户的体验更加平滑。使用它,需要在模板中声明,并包括两个名命插槽:default和fallback。Suspense确保加载完异步内容时显示默认插槽,并将fallback插槽用作加载状态
<tempalte>
<suspense>
<template #default>
<List />
</template>
<template #fallback>
<div>
Loading...
</div>
</template>
</suspense>
</template>
vue3中增加了一个名为Suspense的内置组件,用于在异步组件加载完成前显示占位符,避免页面空白或显示错误信息,提高用户体验。
注意:是一项实验性功能,它不一定灰最终成为稳定功能,并且在稳定之前相关API也可能会发生变化。
<template>
<div>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</div>
</template>
<script setup lang="ts">
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue'))
</script>
注意事项:
Suspense
组件只能包含一个异步组件,否则会抛出错误fallback
插槽中的内容只能是一个单独的元素,否则会抛出错误defineAsynsComponent
方法定义,否则无法使用Suspense组件。Vue2 响应式原理的基础是Object.defineProperty
Vue3的响应式原理是Proxy
Object.defineProperty基本用法:直接在一个对象上定义新的属性或修改现有的属性,并返回对象
// 创建一个空对象
const person = {};
// 使用 Object.defineProperty 定义属性
Object.defineProperty(person, 'name', {
value: 'John', // 属性值
writable: false, // 不可写
enumerable: true, // 可枚举
configurable: false // 不可配置
});
// 尝试修改属性值会失败,因为属性是不可写的
person.name = 'Mike';
// 遍历对象的属性
for (let key in person) {
console.log(key); // 输出 'name',因为属性是可枚举的
}
// 删除属性会失败,因为属性是不可配置的
delete person.name;
// 重新定义属性为可配置
Object.defineProperty(person, 'name', {
configurable: true
});
// 现在可以成功删除属性
delete person.name;
vue2核心源码:
function defineReactive(obj, key, val) {
//创建一个依赖实例,每个属性对象一个依赖
// 一 key 一个 dep
const dep = new Dep()
// 获取 key 的属性描述符,发现它是不可配置对象的话直接 return
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) { return }
// 获取 getter 和 setter,并获取 val 值(以及初始化值)
const getter = property && property.get
const setter = property && property.set
if((!getter || setter) && arguments.length === 2) { val = obj[key] }
// 递归处理,保证对象中所有 key 被观察
let childOb = observe(val)
Object.defineProperty(obj, key, {
//属性可枚举
enumerable: true,
//属性可配置
configurable: true,
// get 劫持 obj[key] 的 进行依赖收集
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val
if(Dep.target) {
// 依赖收集,将当前依赖(观察者)添加到属性的依赖列表中
dep.depend()
if(childOb) {
// 针对嵌套对象,依赖收集
childOb.dep.depend()
// 如果属性值是数组 还需要触发数组响应式
if(Array.isArray(value)) {
dependArray(value)
}
}
}
}
//返回属性值
return value
})
// set 派发更新 obj[key]
set: function reactiveSetter(newVal) {
//在这里实现属性的设置,包括触发响应式更新
...
if(setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 新值设置响应式
childOb = observe(val)
// 依赖通知更新
dep.notify()
}
}
这段代码的主要功能是将对象的属性变成响应式的,当属性的值来发生变化时,可以自动通通知相关的视图进行更新。它通过Object.defineProperty来定义属性的get和set方法,以及属性的特性(例如可枚举、可配置等)来实现这一功能。同时它还支持嵌套对象和数组的响应式处理。
相比于Vue2,Vue3整体体积变小了,移除了一些不常用的API,任何一个函数,比方说ref、reavived、computed等,仅仅在用到的时候才打包,没用到的模块都去掉,打包整体体积变小,最重要的是Tree shanking
Tree shanking是一种通过清除多余代码方式来优化项目打包体积的技术,专业术语叫Dead code elimination
Tree shaking是基于ES6模板语法(import与exports),主要是借助ES6模块的静态编译思想,在编译时就能确定模块的依赖关系,以及输入和输出的变量
Tree shaking做了两件事:
vue3在diff算法中相比vue2增加了静态标记, 其作用是为了会发生变化的地方添加一个flag标记,下次发生变化的时候直接找该地方进行比较,提高性能。
Vue3对不参与更新的元素,会做静态提升只会被创建一次,在渲染时直接复用,这样就免去了重复的创建节点,大型应用会受益于这个改动,免去了重复的创建操作,优化了运行时候的内存占用
没有做静态提升之前
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode("span", null, "你好"),
_createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
], 64 /* STABLE_FRAGMENT */))
}
做了静态提升之后
const _hoisted_1 = /*#__PURE__*/_createVNode("span", null, "你好", -1 /* HOISTED */)
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_hoisted_1,
_createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
], 64 /* STABLE_FRAGMENT */))
}
// Check the console for the AST
静态内容_hoisted_1被放置在render 函数外,每次渲染的时候只要取 _hoisted_1 即可
同时 _hoisted_1 被打上了 PatchFlag ,静态标记值为 -1 ,特殊标志是负整数表示永远不会用于 Diff
在Vue2 中,数据劫持就是通过Object.defineProperty,这个API有一些缺陷:检测不到对象属性的添加和删除
数组Api方法无法监听到,需要对每个属性进行遍历监听,如果嵌套对象,需要深层监听,造成性能问题
Object.defineProperty(data, 'a',{
get(){
// track
},
set(){
// trigger
}
})
Proxy直接可以劫持整个对象,并返回一个新对象,我们可以只操作新的对象达到响应式目的
function reactive(obj) {
if (typeof obj !== 'object' && obj != null) {
return obj
}
// Proxy相当于在对象外层加拦截
const observed = new Proxy(obj, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
console.log(`获取${key}:${res}`)
return isObject(res) ? reactive(res) : res
}
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver)
console.log(`设置${key}:${value}`)
return res
},
deleteProperty(target, key) {
const res = Reflect.deleteProperty(target, key)
console.log(`删除${key}:${res}`)
return res
}
})
return observed
}
同时Proxy 并不能监听到内部深层次的对象变化,而 Vue3 的处理方式是在 getter 中去递归响应式,这样的好处是真正访问到的内部对象才会变成响应式,而不是无脑递归
Proxy的作用
在vue中 Proxy主要用于监听对象的变化,以便在对象属性发生变化时,自动更新相关的视图。这是Vue实现响应式数据绑定的核心机制之一