实现readonly功能
readonly是和reactived很类似但是不可以修改 所以就不需要依赖的收集和触发,因此他的代码十分简单
首先还是附上测试代码:
readonly.spec.ts
import { readonly } from "../reactive"
describe('readonly',()=>{
it('happy path',()=>{
// not set readonly是和reactived很类似但是不可以修改 所以就不需要依赖的收集和触发
const original = {foo:1,bar:{baz:2}}
const wrapped = readonly(original)
expect(wrapped).not.toBe(original)
expect(wrapped.foo).toBe(1)
})
})
reactive.ts
export function readonly(raw){
return new Proxy(raw,{
get(target,key){
const res = Reflect.get(target,key)
return res
},
set(target,key,value){
return true
}
})
}
看了代码后,是不是觉得其实就是把reactive的依赖收集和触发依赖去掉,然后把set功能去掉就没了,确实是这样的。当时这节就这样结束也太短了吧,所以,不存在结束的,长起来。
重构
先来看看我们现在reactive.ts中的代码
是不是觉得这些get和set的代码非常的相似,做的非常像,没错,这就是我们今天要重构的地方。
首先我们先对get函数进行重构,一开始想着肯定是在外部定义一个function get函数抽离get的逻辑,然后分别给reactive和readonly所以就有了这个get函数
但是仔细一想其实reactive和readonly的get还是不一样的,所以我们就使用一个createGetter这样的高级函数去将get函数返回出来,然后我们给createGetter这个函数去传入isReadonly,区别reactive和readonly。
既然get都抽离完了,那就到set了,基本上想法和get一样的,甚至比get简单,因为readonly中没有set功能,所以函数中甚至都不需要判断
这样也就抽离完毕了,比起一开始reactive和readonly中简洁多了
但是其实这样看来我们reactive中的代码还是挺多的,这时候我们还是可以再抽离的,我们可以把proxy中的handler函数抽离到baseHandlers文件中去,抽离完之后就很简洁了,就只剩下几行代码了,get set的逻辑都抽到baseHandlers中去了
这样看我们的优化应该就快到头了,不过,我们还要再来看看性能方面:
红框中的写法会让我们每调用到new Proxy就会掉一次createGetter/createSetter函数,所以我们其实可以把他抽到外面,让它初始化的时候调用,减少他的调用次数,如下图
我们还可以new Proxy这个抽离出来,如图
最后的最后,我们需要给readonly的set方法做一个处理,如果是readonly而且调用了set方法,就给他爆出一个警告,我们先写测试用例:
it('warn then call set',()=>{
console.warn = jest.fn()
const user = readonly({
age:10
})
user.age = 11
expect(console.warn).toBeCalled()
})
这时候我们只需要给readonly的set函数中调用一次console.warn就可以爆出警告通过测试用例
接下来就给出几个修改文件的代码
reactive.ts
import { mutableHandlers, readonlyHandlers } from "./baseHandlers"
export function reactive(raw){
return createReactiveObject(raw,mutableHandlers)
}
export function readonly(raw){
return createReactiveObject(raw,readonlyHandlers)
}
function createReactiveObject(target,baseHandlers){
return new Proxy(target,baseHandlers)
}
baseHandlers.ts
import { track, trigger } from "./effect"
const get = createGetter()
const set = createSetter()
const readonlyGet = createGetter(true)
function createGetter(isReadonly = false){
return function get(target,key){
const res = Reflect.get(target,key)
if(!isReadonly) {
track(target,key)
}
return res
}
}
function createSetter() {
return function set(target,key,value){
const res = Reflect.set(target,key,value)
trigger(target,key)
return res
}
}
export const mutableHandlers = {
get:get,
set:set
}
export const readonlyHandlers = {
get:readonlyGet,
set(target,key,value){
console.warn(`key:${key} set 失败 因为 target 是readonly`,target)
return true
}
}