Vue3源码实现(二)—— 实现reactive、effect、依赖收集、触发依赖
Vue3 reactive 分析
Vue3源码中,reactive 是通过Proxy
实现的,所以平时我们在写代码的时候,把一个对象写到reactive里面会变成响应式,其实现机制就在于Proxy
的get
和set
,如果是获取响应式对象的值,那么就会触发get
方法,如果是设置值,那么就会触发set
方法。
Vue3 effect 分析
先写一段测试代码看下
describe('effect', () => {
it('测试 effect 触发', () => {
const user = reactive({
age: 10
});
let nextAge = 0;
effect(() => {
nextAge = user.age + 1;
})
expect(nextAge).toBe(11);
user.age++;
expect(nextAge).toBe(12);
})
})
在这里,user
对象用了reactive
就会变成一个响应式对象。一开始初始化,给了一个属性age
,并赋值10
。
接着,写了一个effect
监听函数,其参数就是一个函数,且函数会立即执行,函数执行后,nextAge
的值也就有了,就是 user.age + 1
。
注意这里,user.age + 1
会发生什么事呢?这里user.age
是获取值,也就会触发user
这个响应式对象的get
方法。
接着,我们再往下看那段测试代码。
user.age++;
expect(nextAge).toBe(12);
user.age++
说明user.age
有变,那么就会触发set
方法。
而在这里,nextAge
依赖于user.age
的变化,因为在effect
的作用是,一旦user.age
发生改变,那么nextAge
的值也就会相应改变。
那么,问题就来了,这个effect
是怎么实现监听到响应式对象的某个属性发生了变化,然后完成监听,触发相应函数执行的呢?
这就得说到依赖收集和触发依赖了。
依赖收集
在触发了响应式对象的get
函数时,就需要在get
函数内部实现依赖收集,把这个响应式对象、对象的key形成一个Map结构的对应关系
let targetMap = new Map();
let depsMap = targetMap.get(target);
if(!depsMap) {
depsMap = targetMap.set(target, depsMap);
}
let dep = depsMap.get(key);
if(!dep) {
dep = new Set();
dep.set(key, dep);
}
dep.add(activeEffect);
activeEffect
是一个全局变量,下面会讲到它是啥。
触发依赖
在触发了响应式对象的set
函数时,就需要在set
函数内部实现触发依赖,让响应式数据在变化时,能及时触发调用,让依赖于这个响应式数据的数据发生改变,也就是能够再次执行一次effect
函数。
那么在依赖收集和触发依赖之间,就要共同维护一个变量targetMap的变量,所以targetMap是一个全局变量。targetMap.get(target)
获取到depsMap
,然后根据发生了改变的key
,定位找到依赖集const dep = depsMap.get(key)
,最终循环遍历依赖集,一一触发。
const depsMap = targetMap.get(target);
const dep = depsMap.get(key);
for(let effect of dep) {
effect.run();
}
dep是一些收集到的关于target对象中某个key的依赖,dep的每一个元素是一个表示当前effect
的实例。
我把effect
函数内部的实现写一下,应该会清晰点。
export function effect(fn: any) {
let _effect = new ActiveEffect(fn);
_effect.run();
}
effect
是每执行一次,就实例化一个effect
对象,所以每次执行effect
函数都可以获得当前的effect
实例。
再看run
内部实现:
let activeEffect: any;
class ActiveEffect {
private _fn;
constructor(fn: any) {
this._fn = fn;
}
run() {
activeEffect = this;
this._fn();
}
}
在执行run
的时候,会把activeEffect
赋值给当前effect
实例对象:
activeEffect = this;
然后执行this._fn()
,也就是effect
函数的参数(一个函数),这会触发依赖收集,把当前的effect
实例添加到key的dep依赖中。
dep.add(activeEffect);
也就是上面track函数那里为啥写了这句代码的原因。
关于reactive、effect、依赖收集、触发依赖的实现,上面已经分析完了。
下面附上完整的代码。
effect.ts
let activeEffect: any;
class ActiveEffect {
private _fn;
constructor(fn: any) {
this._fn = fn;
}
run() {
activeEffect = this;
this._fn();
}
}
export function effect(fn: any) {
let _effect = new ActiveEffect(fn);
_effect.run();
}
let targetMap = new Map();
export function track(target: any, key: any) {
// target -> key -> dep
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let dep = depsMap.get(key);
if (!dep) {
dep = new Set();
depsMap.set(key, dep);
}
dep.add(activeEffect);
}
export function trigger(target: any, key: any) {
const depsMap = targetMap.get(target);
const dep = depsMap.get(key);
for(let effect of dep) {
effect.run();
}
}
reactive.ts
import { track, trigger } from './effect';
export function reactive(target: any) {
return new Proxy(target, {
get(target, key) {
// const res = target[key];
// 使用 Reflect.get
const res = Reflect.get(target, key);
// 依赖收集
track(target, key);
return res;
},
set(target, key, value) {
// target[key] = value;
Reflect.set(target, key, value);
// 触发依赖
trigger(target, key);
return true;
}
})
}
下一次更新的内容是:实现 effect 返回 runner。
生命不息,奋斗不止
转载自:https://juejin.cn/post/7141787461461475365