依赖收集

第一步

先明确依赖收集时候,数据的格式:他是一个 Map 格式的,{ obj: { 属性: Map: { effect1, effect2 } } }

1
2
3
4
5
6
7
8
9
10
{
{ name: "xiaoming", age: 18 }: {
name: {
effect
},
age: {
effect1, effect2
}
}
}

reactiveEffect.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { activeEffect } from './effect';

// 存放依赖收集的内容
const targetMap = new WeakMap();

export function track(target, key) {
if (activeEffect) {
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}

let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, new Map());
}
}
}

第二步

新增移除映射表中属性所对应的 effect,用于清理不需要的属性

reactiveEffect.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import { activeEffect } from './effect';

// 存放依赖收集的内容
const targetMap = new WeakMap();

// -----------------------------新增开始-----------------------------
export const createDep = (cleanup, key) => {
const dep = new Map() as any;
dep.cleanup = cleanup;
dep.name = key;
return dep;
};
// -----------------------------新增结束-----------------------------

export function track(target, key) {
if (activeEffect) {
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}

let dep = depsMap.get(key);
if (!dep) {
// -----------------------------新增开始-----------------------------
depsMap.set(key, dep = createDep(() => depsMap.delete(key), key));
// -----------------------------新增结束-----------------------------
}
}
}

第三步

关联 effect 和收集器

reactiveEffect.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// -----------------------------新增开始-----------------------------
import { activeEffect, trackEffect } from './effect';
// -----------------------------新增结束-----------------------------

// 存放依赖收集的内容
const targetMap = new WeakMap();

export const createDep = (cleanup, key) => {
const dep = new Map() as any;
dep.cleanup = cleanup;
dep.name = key;
return dep;
};

export function track(target, key) {
if (activeEffect) {
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}

let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, dep = createDep(() => depsMap.delete(key), key));
}

// -----------------------------新增开始-----------------------------
// 将当前的 effect 放入到 dep 中,后续可以根据值的变化触发此 dep 中存放的 effect
trackEffect(activeEffect, dep);
// -----------------------------新增结束-----------------------------
console.log(targetMap);
}
}

effect.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...

class ReactiveEffect {
// -----------------------------新增开始-----------------------------
// 用于记录当前 effect 执行了多少次
_trackId = 0;
// 用来记录存放了哪些 effect
deps = [];
_depsLength = 0;
// -----------------------------新增结束-----------------------------

...
}

// -----------------------------新增开始-----------------------------
export function trackEffect(effect, dep) {
// 双向记忆

// dep 中关联 effect
dep.set(effect, effect._trackId);
// effect 同样关联 dep
effect.deps[effect._depsLength++] = dep;
}
// -----------------------------新增结束-----------------------------

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app"></div>

<script type="module">
import { reactive, effect } from './reactivity.js';

// let obj = { name: 'xiaoming', age: 18, flag: true, address: { name: 1 } };
let obj = { name: 'xiaoming', age: 18 };
const state = reactive(obj);

effect(() => {
app.innerHTML = `姓名${state.name}, 年龄:${state.age}`
})

effect(() => {
app.innerHTML = `姓名${state.name}`
})

setTimeout(() => {
state.age++;
}, 1000);
</script>
</body>
</html>

img_1.png

第四步

触发更新,找到哪个对象上的哪个属性中的哪个 effect,然后让这个 effect 重新执行

触发操作会进入到 set

baseHandler.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { ReactiveFlags } from './constants';
import { activeEffect } from './effect';
import { track, trigger } from './reactiveEffect';

export const mutableHandlers: ProxyHandler<any> = {
...

// -----------------------------新增开始-----------------------------
set(target, key, value, receiver) {
// 拿到老的值
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
if (oldValue !== value) {
// 老值和新值不一样的时候,需要触发页面更新
trigger(target, key, value, oldValue);
}

return result;
}
// -----------------------------新增结束-----------------------------
};

reactiveEffect.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export function trigger(target, key, value, oldValue) {
const depsMap = targetMap.get(target);
// 该对象从来没有被收集过,直接返回
if (!depsMap) {
return;
}

const dep = depsMap.get(key);
// 触发更新的属性在之前被收集过
if (dep) {
// 触发该属性对应的所有的 effect
triggerEffects(dep);
}
}

effect.ts

在最开始创建 effect 对象的时候,effect 对象中 scheduler 保存了用户触发更新的方法,所以在属性再次触发更新的时候,直接调用对应的 scheduler 即可

1
2
3
4
5
6
7
export function triggerEffects(dep) {
for (const effect of dep.keys()) {
if (effect.scheduler) {
effect.scheduler();
}
}
}

img.png