computed 实现

第一步

计算属性维护了一个 dirty 属性,默认值是 true,运行过一次后,会将 dirty 变为 false, 并且当依赖的值变化后,会让 dirty 再次变为 true

计算属性是具备依赖收集的能力,可以收集对应的 effect,依赖的值变化后会触发 effect 重新执行

packages/reactivity/src/constants.ts

1
2
3
4
5
6
7
8
...

export enum DirtyLevels {
// 脏值,意味着取值的时候要运行计算属性
Dirty = 4,
// 不脏,就使用上一次的结果
NoDirty = 0
}

packages/reactivity/src/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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class ReactiveEffect {
// 用于记录当前 effect 执行了多少次
_trackId = 0;
// 用来记录存放了哪些 effect
deps = [];
_depsLength = 0;
_running = 0;

// -----------------------------新增开始-----------------------------
_dirtyLevel = DirtyLevels.Dirty;
// -----------------------------新增结束-----------------------------

// 默认创建的 effect 是响应式的
public active = true;

// fn 是用户编写的函数
// 如果 fn 中依赖的数据发生变化后,需要重新调用 run
constructor(public fn, public scheduler) {
}

// -----------------------------新增开始-----------------------------
public get dirty() {
return this._dirtyLevel === DirtyLevels.Dirty;
}

public set dirty(value) {
this._dirtyLevel = value ? DirtyLevels.Dirty : DirtyLevels.NoDirty;
}

run() {
// 每次运行后,effect 就变为 no_dirty
this._dirtyLevel = DirtyLevels.NoDirty;
// -----------------------------新增结束-----------------------------

// 不是激活的,就什么都不做
if (!this.active) {
return this.fn();
}

...
}
}

第二步

computed.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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import { isFunction } from '@vue/shared';
import { ReactiveEffect } from './effect';
import { trackRefValue, triggerRefValue } from './ref';

class ComputedRefImpl {
public _value;
public effect;
public dep;

constructor(getter, public setter) {
this.effect = new ReactiveEffect(() => getter(this._value), () => {
// 计算属性依赖的值变换了,应该触发渲染
triggerRefValue(this)
});
}

get value() {
if (this.effect.dirty) {
this._value = this.effect.run();

// 如果在 effect 中访问了计算属性,那么计算属性是可以收集这个 effect 的
trackRefValue(this);
}
return this._value;
}

set value(value) {
this.setter(value);
}
}

export function computed(getterOrOptions) {
const onlyGetter = isFunction(getterOrOptions);
let getter;
let setter;

if (onlyGetter) {
getter = getterOrOptions;
setter = () => {
};
} else {
getter = getterOrOptions.get;
setter = getterOrOptions.set;
}

return new ComputedRefImpl(getter, setter);
}