前言
上一篇中我们主要讲的Vue3.0
响应式的实现,其主要是利用Proxy
的来对数据进行劫持。本节课接上一章,主要是讲解Vue3.0
是如何在被劫持时进行收集依赖的,同时在修改时是如何派发更新的。
我个人推荐的看源码的方式最好是通过视频文章自己能手写一边然后再去看,否则就会造成这样的结果。
真是卷...
在
Vue 3.0
中没有了watcher
的概念,但是取而代之的是一个叫effect
的东西 ,所以接下来讲的东西会和effect
有关。
正文
effect(类似于Watcher)
ini
// reactivity/effect.js
export function effect(fn, options = {}) { // 类似于vue2的watcher
const effect = createReactiveEffect(fn, options);
if (!options.lazy) {
effect();
}
return effect;
}
let uid = 0; // effect的id
let activeEffect; // 保存当前的effect
const effectStack = []; // effect栈结构
function createReactiveEffect(fn, options = {}) {
const effect = function reactiveEffect() {
if (!effectStack.includes(effect)) {
try {
activeEffect = effect; // 当前最顶层的effect
effectStack.push(activeEffect); // 入栈
return fn(); // 执行用户的方法
} finally {
// 出栈
effectStack.pop(); // 出栈
activeEffect = effectStack[effectStack.length - 1]; // 重置栈结构
}
}
}
effect.id = uid++; // 用于区别effect
effect._isEffect = true; // 用户区分我们effect是不是响应式的
effect.raw = fn; // 保存用户的方法
effect.options = options; // 保存用户的属性
effect.active = true;
return effect;
}
组件在初始化渲染的时候会创建一个effect
,和Watcher
会传入一个函数。当这个effect
被执行的时候会存入全局栈,初次创建effect
的时候会判断当前栈是否存在当前effect
。文中的activeEffect
和Vue2.0
中的Dep.target
是一个意思,都是指的当前执行的作用域。
依赖收集Track
csharp
// reactivity/effect.js
let targetMap = new WeakMap(); // 存储effect的一种结构
export function Track(target, type, key) { // target:目标对象 key: 对象中的值
if (!activeEffect) return
let depMap = targetMap.get(target);
if (!depMap) {
targetMap.set(target, (depMap = new Map()))
}
let dep = depMap.get(key);
if (!dep) {
depMap.set(key, (dep = new Set()))
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect); // 收集effect
}
}
Vue3.0
的依赖收集和Vue2.0
的有所区别,vue2
中是利用闭包
对每个属性都创建了一个dep
来进行依赖收集。而在Vue3
中是创建了一种特殊的存储结构:目标对象 --> 对象属性key --> effect数组。
在GET处收集
javascript
// reactivity/baseHandlers.js
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
/*
*******
*******此处省略
*/
if (!isReadonly) {
// 收集依赖
Track(target, TrackOpTypes.GET, key); // +++++++++++++++++
}
return res;
}
}
在Proxy
中,如果不是只读就对当前属性进行依赖收集。
派发更新trigger
scss
// reactivity/baseHandlers.js
// 触发更新
export function trigger(target, type, key, newValue, oldValue) {
const depsMap = targetMap.get(target); // 获取当前对象的属性集合
if (!depsMap) return;
let effects = new Set(); // 需要执行的effect执行列
const add = (effectAdd) => { // 把effect添加到effect执行列中
if (effectAdd) {
effectAdd.forEach(effect => {
effects.add(effect);
})
}
}
add(depsMap.get(key)); // 根据属性获取当前属性的effect列
if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => { // 如果修改的是数组的长度,这里需要做特殊处理
if (key === 'length' || key >= newValue) {
add(dep)
}
})
} else {
if (key !== undefined) {
add(depsMap.get(key));
}
switch (type) {
case TriggerOpTypes.ADD: {
if (isArray(target) && isIntergetKey(key)) {
add(depsMap.get('length'))
}
break;
}
}
}
effects.forEach(effect => { // 执行effect
if (effect.options.scheduler) { // 在computed和watch中会执行
effect.options.scheduler(effect);
} else {
effect();
}
})
}
在 trigger()
函数,首先获取当前 targetMap
中 data
对应的主题对象的 depsMap
,而这个 depsMap
即我们在依赖收集时在 track
中定义的。
测试
xml
<body>
<div id="app"></div>
<button id="updateButton">改变</button>
</body>
ini
let state = reactive({
age: 22,
name: '小明'
});
const el = document.getElementById('app')
effect(() => { // 模拟页面使用响应式数据
el.innerHTML = state.age;
})
document.getElementById('updateButton').onclick = function() {
state.age++;
}
我们使用effect
来模拟我们在页面使用reactive
的响应式数据,因为目前还没有实现模版编译以及渲染的代码,发现我们点击按钮页面会一直更新。
小结
对比Vue2.0
通过Object.defineProperty
在数据定义时为指定具体属性添加 getter/setter
拦截、为每个对象添加deps
依赖数组的响应式系统实现方案。Vue3.0
采用Proxy
结合WeakMap、WeakSet
,通过代理在运行时拦截对象的基本操作来收集依赖并全局管理,在性能上就得到了很大的提升,也为开发者带来了更好的开发体验。
如果觉得本文有帮助 记得点赞三连哦 十分感谢!