重构reactive

本来我们应该直接实现readonly模块的,但是其实它本质上和reactive很像,只不过它是只读的,不能set,set时,不会设置成功只会警告,所以相似代码很多,我们开发之前先重构一下reactive

分析一下这个响应式场景下proxy的两个形参,影响最主要的是第二个参数handler, 也就是一个包含get set函数的对象

1
2
3
4
{
  get(target, key) {},
  set(target, key, value) {},
}

所以我们先把这部分拆分出去,在reactivity目录下创建baseHandler.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// reactive.ts 改造如下
import {
  mutableHandler,
  mutableHandlerReadonly,
} from './baseHandler'

function reactive(raw) {
  return createReactiveObject(raw, mutableHandler)
}

function readonly(raw) {
  return creativeReactiveObject(raw, mutableHandlerReadonly)
}

// 根据不同的handler统一创建代理对象,相当于中间层,解耦响应式和handler处理器
function creativeReactiveObject(target, baseHandler) {
  return new Proxy(target, baseHandler)
}

上面的mutableHandler,mutableHandlerReadonly就是要重点重构的两个函数,导出的这两个对象就是上面分析的这类对象:

1
2
3
4
{
  get(target, key) {},
  set(target, key, value) {},
}

详细的代码逻辑:

 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
// baseHandler.ts
import { trigger, track } from './effect'

// 这样写的目的是能缓存这些函数值,只在文件初始化时加载一次即可
const get = createGetter()
const getReadonly = createGetter(true)
const set = createSetter()

function createGetter(isReadonly: Boolean = false) {
  return function get(target, key) {
    const value = Reflect.get(target, key)

    if(!isReadonly) {
      // 不是readonly才需要收集依赖
      track(target, key)
    }

    return value
  }
}

function createSetter() {
  return function set(target, key, value) {
    const value = Reflect.set(target, key, value)

    trigger(target, key)
    return value
  }
}

const mutableHandler = {
  get,
  set,
}

const mutableHandlerReadonly = {
  get: getReadonly,
  set(target, key, value) {
    console.warn(`key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, `${JSON.stringfy(target)}`)
    return true
  }
}

export {
  mutableHandler,
  mutableHandlerReadonly,
}

实现readonly

其实经过上面的重构,我们已经实现完了readonly(),它是响应式核心之一。而且后期我们实现新的代理会变的很容易,直接增加对应的baseHandler即可,先看一下测试用例的用法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { readonly } from "../reactive";

describe("readonly", () => {
  it('happy path', () => {
    const origin = {
      foo: 1
    }

    const observed = readonly(origin)
    expect(observed.foo).toBe(1)
    expect(observed).not.toBe(origin)
  });
  it('console warn when you set value', () => {
    const origin = {
      foo: 1
    }
    console.warn = jest.fn()
    const observed = readonly(origin)
    observed.foo = 2
    expect(console.warn).toHaveBeenCalled()
  });
})

其实readonly几乎和reactive一样,都是响应式的,只是不能修改属性值。