VUE3 原理和更新梳理

497 阅读3分钟

框架核心

Vue2 是响应式原理基于 Object.defineProperty 方法重定义对象的 getter 与 setter,vue3 则基于 Proxy 代理对象,拦截对象属性的访问与赋值过程。差异在于,前者并不能对诸如数组长度变化、增删元素操作、对象新增属性进行感知,在 vue 层面不得不重写一些数组方法(push、pop、unshift、shift 等),动态添加响应式属性,也要使用 $set方法等。 而 Proxy 则完美地从根上解决了这些问题,不过对于不支持 Proxy 对象的浏览器(如IE),如果要使用 vue3 依然要进行降级兼容

1. Object.defineProperty
// 假设我们在 data 函数中返回的数据为 initData
const initData = { value: 1 };
// 基于 initData 创建响应式对象 data
const data = {};
Object.keys(initData).forEach(key => {
	Object.defineProperty(data, key, {
		get() {
			// 此处依赖收集
			console.log('访问了', key);
			return initData[key];
		},
		set(v) {
			// 此处执行回调更新
			console.log('访问了', key);
			initData[key] = v;
		}
	});
});
// 从这里可以看出,initData 动态添加的属性,并不能被观测到,这也是 Vue.$set 存在的原因。

2. Proxy
const initData = { value: 1 };
const proxy = new Proxy(initData, {
get(target, key) {
// 此处依赖收集
console.log('访问了', key);
return target[key];
},
set(target, key, value) {
// 此处执行回调更新
console.log('修改了', key);
return Reflect.set(target, key, value);
}
});
// Proxy 可以观测到动态添加的属性

vue3 新特性

1.异步组件需要使用 defineAsyncComponent 创建

全局注册

const AsyncComp = defineAsyncComponent(() =>
	import('./components/AsyncComp.vue')
)
app.component('async-component', AsyncComp)

局部声明

const AsyncComp = defineAsyncComponent(() =>
	import('./components/AsyncComp.vue')
)
...
export default {
components: { 'async-comp': AsyncComp }
}

1.自定义指令更新

**自定义指令的钩子**
// vue2
bind - 指令绑定到元素后发生。只发生一次。
inserted - 元素插入父 DOM 后发生。
update - 当元素更新,但子元素尚未更新时,将调用此钩子。
componentUpdated - 一旦组件和子级被更新,就会调用这个钩子。
unbind - 一旦指令被移除,就会调用这个钩子。也只调用一次。.
// vue3
bind → beforeMount
inserted → mounted
beforeUpdate:新的!这是在元素本身更新之前调用的,很像组件生命周期钩子。
update → 移除!有太多的相似之处要更新,所以这是多余的,请改用 updated。
componentUpdated → updated
beforeUnmount:新的!与组件生命周期钩子类似,它将在卸载元素之前调用。
unbind -> unmounted

2.函数式组件

// vue2 Functional.vue
<template>
	<div>{{ msg }} </div>
</template>
<script>
export default {
	functional: true,
	props: ['msg']
}
</script>

// vue3 Functional.js
import { h } from 'vue'
export default function Functional(props, context) {
	return h('div',context.attrs,props.msg)
}
Functional.props = ['msg']

3.组合 API--setup 函数

组件创建前执行的初始化函数,默认参数包含 props 和 context。context 可以理解为 组件实例挂载之前的上下文(虽然这么理解,但不是实例本身,这点要清楚),所以实例上 的 attrs,slots,emit 可以在 context 上访问到

当使用vue3的Compostion API时,如果还是用Vue2的形式组织代码,这不但不会提升代码质量,反而因为缺乏约束而降低可读性。

compostion API一个特点是提升逻辑复用,这是没有错的,但是当时我有一个错误观点,就是只有复用的逻辑才应该封装到hook中。我们还是回到Vue的官方例子,你会发现他把原来放在一个vue文件的逻辑拆分到composables目录,目录下分别定义一个文件,表示不同的逻辑关注点,提高项目长期维护性的

基于 setup 方法的生命周期钩子对应更新

beforeCreate -> setup()
created -> setup()

beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeUnmount -> onBeforeUnmount
unmounted -> onUnmounted
errorCaptured -> onErrorCaptured // 错误上报钩子,仅做了解
renderTracked -> onRenderTracked // { key, target, type } 仅做了解
renderTriggered -> onRenderTriggered // { key, target, type

4.跨组件传递数据

// 常规组件中
provide() {
	return {
		state: this.state,
		constant: '常量'
	}
}

// setup 方法中
import { provide, readonly, inject } from 'vue'
provide(key, reactiveValue)
// 禁止子组件对 reactiveValue 进行修改的话
provide(key, readonly(reactiveValue))
// 子组件使用数据
{
	inject: [key],
}
// 或使用组合 API
inject('state'); 

简化:单文件组件 < script setup> defineProps,defineEmits 直接在< script setup>中使用,无须导入。 useSlots 和 useAttrs 是 真 实 的 运 行 时 函 数 , 它 会 返 回与 setupContext.slots 和 setupContext.attrs 等价的值,同样也能在普通的组合式 API中使用

// 在<script setup>内部的顶层变量,均能直接用于模板部分,
// 省略了 setup 函数及其返回值
<template>
<span @click="increment">{{count}}</span>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
const increment = () => {
	count.value++
}
</script>