在本小节中,我们将会实现 readonly API
it('happy path', () => {
// not set
const original = { foo: 1, bar: 2 }
const wrapped = readonly(original)
expect(wrapped).not.toBe(original)
expect(wrapped.bar).toBe(2)
wrapped.foo = 2
// set 后不会更改
expect(wrapped.foo).toBe(1)
})
我们知道 readonly 和 reactive 的实现原理是一致的,都可以通过 Proxy 来实现一个包装类,唯一的区别在于,readonly 的不会被 track,而且 readonly 的属性值不可更改
那么该如何实现呢?
// reactive.ts
export function readonly(raw) {
return new Proxy(raw, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
return res
},
set() {
return true
},
})
}
在这个版本下,我们就实现了最简单的 readonly 的实现。但是我们可以发现其实 reactive 和 readonly 的部分代码是一样的,就可以提取重复代码变为函数:
import { track, trigger } from './effect'
// version 2 版本就可以将重复的代码提取出来
// 作为 createGetter 和 createSetter
function createGetter(isReadonly = false) {
return function get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
if (!isReadonly) {
track(target, key)
}
return res
}
}
function createSetter() {
return function set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver)
trigger(target, key)
return res
}
}
export function reactive(raw) {
return new Proxy(raw, {
get: createGetter(),
set: createSetter(),
})
}
export function readonly(raw) {
return new Proxy(raw, {
get: createGetter(true),
set() {
return true
},
})
}
为了更好的管理代码,在 v3 中,我们还可以直接将 createSetter 和 createGetter 分层出去
// reactivity/baseHandlers.ts
import { track, trigger } from './effect'
const get = createGetter()
const readonlyGet = createGetter(true)
const set = createSetter()
function createGetter(isReadonly = false) {
return function get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
// 在 get 时收集依赖
if (!isReadonly) {
track(target, key)
}
return res
}
}
function createSetter() {
return function set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver)
// 在 set 时触发依赖
trigger(target, key)
return res
}
}
// mutable 可变的
export const mutableHandlers = {
get,
set,
}
export const readonlyHandlers = {
get: readonlyGet,
set(target, key, value) {
return true
},
}
import { mutableHandlers, readonlyHandlers } from './baseHandlers'
export function reactive(raw) {
return new Proxy(raw, mutableHandlers)
}
export function readonly(raw) {
return new Proxy(raw, readonlyHandlers)
}
这样就可以将实现与入口分离开来了。下面我们看到其实 reactive 和 readonly 它的创建方式是差不多的都是通过 new Proxy 的方式来创建,那么这些步骤我们也可以来分离开
// reactive.ts
import { mutableHandlers, readonlyHandlers } from './baseHandlers'
function createActiveObject(raw, baseHandlers) {
return new Proxy(raw, baseHandlers)
}
export function reactive(raw) {
return createActiveObject(raw, mutableHandlers)
}
export function readonly(raw) {
return createActiveObject(raw, readonlyHandlers)
}
这样我们就实现了最少的代码了
这个单元测试,我们要让用户在设置一个 readonly prop value 时报一个警告
it('should warn when update readonly prop value', () => {
// 这里使用 jest.fn
console.warn = jest.fn()
const readonlyObj = readonly({ foo: 1 })
readonlyObj.foo = 2
expect(console.warn).toHaveBeenCalled()
})
这里我们发现实现警告还是非常简单的,只需要找到 readonly proxy 的 set 即可
// baseHandlers.ts
export const readonlyHandlers = {
get,
set(target, key, value) {
// 在这里警告
console.warn(
`key: ${key} set value: ${value} fail, because the target is readonly`,
target
)
return true
},
}
这个时候我们再跑一边测试,发现就完全没问题了。