实现computed

本节实现computed(),先看测试用例:

 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
48
describe("computed", () => {
  it("happy path", () => {
    const user = reactive({
      age: 1,
    });

    const age = computed(() => {
      return user.age;
    });

    expect(age.value).toBe(1);
  });

  it("should compute lazily", () => {
    // 计算属性的缓存特性
    const value = reactive({
      foo: 1,
    });
    const getter = jest.fn(() => {
      return value.foo;
    });
    const cValue = computed(getter);
    //cValue.value 就是那个value.foo

    // lazy -> 不获取cValue.value,getter就不会执行
    expect(getter).not.toHaveBeenCalled();

    // 触发cValue.value,get操作后获得1,并且getter执行了
    expect(cValue.value).toBe(1);
    expect(getter).toHaveBeenCalledTimes(1);

    // should not compute again
    cValue.value; // get
    expect(getter).toHaveBeenCalledTimes(1);

    // should not compute until needed
    value.foo = 2; // 触发trigger -> 收集effect -> get 重新执行
    expect(getter).toHaveBeenCalledTimes(1);

    // now it should compute
    expect(cValue.value).toBe(2);
    expect(getter).toHaveBeenCalledTimes(2);

    // should not compute again
    cValue.value;
    expect(getter).toHaveBeenCalledTimes(2);
  });
});

分析一下测试用例发现,computed()ref()很像,都是返回一个带有value属性的对象,但是computed()具有缓存的能力,我们要完善以下细节:

  1. 接受一个函数,返回一个属性,有个value属性
  2. 懒执行机制,如果不获取这个value属性,则不执行传递的函数
  3. 获取了这个value属性后,执行传递的函数一次,并得到返回值
  4. 再次获取value属性,执行传递的函数一次,不应该计算
  5. 依赖的值更新后,执行传递的函数一次,获取value属性,传递的函数调用两次
  6. 再次获取value属性,传递的函数调用两次。