happy path 单元测试
// computed.spec.ts
describe("computed", () => {
it("happy path", () => {
const user = reactive({
age: 10,
});
const age = computed(() => {
return user.age;
});
expect(age.value).toBe(10);
});
});
以上是一个简单的 computed get 操作
代码实现
// computed.ts
class ComputedRefImpl {
private _getter: any;
constructor(getter) {
this._getter = getter;
}
get value() {
return this._getter();
}
}
export function computed(getter) {
return new ComputedRefImpl(getter);
}
should compute lazily 单元测试
// computed.spec.ts
describe("computed", () => {
it("should compute lazily", () => {
const value = reactive({ foo: 1 });
const getter = jest.fn(() => {
return value.foo;
});
const cValue = computed(getter);
// 断言1
// 懒执行 不调用 cValue 不会执行getter
expect(getter).not.toHaveBeenCalled();
expect(cValue.value).toBe(1);
expect(getter).toHaveBeenCalledTimes(1);
// 断言2
// 缓存机制 再次调用cValue.value getter不再执行
cValue.value;
expect(getter).toHaveBeenCalledTimes(1);
// 断言3
value.foo = 2;
expect(getter).toHaveBeenCalledTimes(1);
// 当依赖的响应式对象的值发生改变 再次调用cValue.value getter执行
expect(cValue.value).toBe(2);
expect(getter).toHaveBeenCalledTimes(2);
cValue.value;
expect(getter).toHaveBeenCalledTimes(2);
});
})
断言1
从断言1可以知道, computed 是懒执行,只有当调用 cValue.value 时才会执行 getter
用我们的上面的 happy path 单元测试的实现代码就可以通过
断言2
从断言2可以知道 computed 具有缓存机制,当 cValue.value 重复调用的时候 getter 始终只执行一次
思路:可以通过一个变量控制 getter 的执行
代码实现
// computed.ts
class ComputedRefImpl {
private _getter: any;
private _dirty: boolean = true; // 控制 getter 执行
private _value: any;
constructor(getter) {
this._getter = getter;
}
get value() {
if(this._dirty) {
this._dirty = false;
this._value = this._getter();
}
return this._value;
}
}
export function computed(getter) {
}
断言3
当执行到断言3会报这样一个错误
这是因为当执行 value.foo = 2 的时候会触发 trigger,但是依赖并没有被收集起来
代码实现
// computed.ts
class ComputedRefImpl {
private _dirty: boolean = true;
private _value: any;
private _effect: any;
constructor(getter) {
this._effect = new ReactiveEffect(getter, () => {
if(!this._dirty) {
this._dirty = true;
}
})
}
get value() {
if(this._dirty) {
this._dirty = false;
this._value = this._effect.run();
}
return this._value;
}
}
export function computed(getter) {
return new ComputedRefImpl(getter);
}
这里有几个注意点
- 当响应式对象的值改变, 会执行 run, 从而会执行 getter。这样就违背了 computed 懒执行的初衷
- 当响应式对象的值改变, _dirty 需要重新变为 true 所以这里在 new ReactiveEffect 的时候,放了一个 scheduler 进去,这样当响应式对象的值改变的时候既不会执行 getter,同时也能设置 _dirty 为 true