这一篇主要实现readonly,很简单。再去将之前代码进行重构。
readonly
测试用例
describe('readonly', () => {
it('happy path', () => {
// 创建一个对象
const original = { foo: 1, bar: { baz: 2 } }
// 创建original的只读代理对象
const wrapped: any = readonly(original)
// wrapped不等于original
expect(wrapped).not.toBe(original)
// wrapped.foo === 1
expect(wrapped.foo).toBe(1)
})
it('warn then call set', () => {
console.warn = jest.fn()
const user = readonly({ age: 10 })
// 修改只读代理的属性
user.age = 11
// 调用console.warn警告
expect(console.warn).toBeCalled()
})
})
功能描述
readonly于reactive类似,主要区别就在于readonly的代理对象是只读的,意味着它不能正常触发set。
实现
export function readonly (raw) {
return new Proxy(raw, {
// 与reactive的get相似,因为是只读的所以不需要触发依赖,那么也就不需要收集依赖,所以比reactive的get少了track操作。
get (target, key) {
return Reflect.get(target, key)
},
set () {
console.warn(`key: ${key} set 失败,因为target是readonly`, target)
return true
}
})
}
优化 - 以reactive为例
第一次重构
// reactive/readonly都包含的handler都包含get和set方法,所以将它们抽离出一个方法
// 又因为 reactive/readonly 操作类似,所以可以创建一个高阶函数
function createGetter (isReadonly = false) {
return function get (target, key) { // 返回一个函数
const res = Reflect.get(target, key)
if (!isReaconly) { // 如果不是只读的
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
}
}
// 更新后
function readonly (raw) {
return new Proxy(raw, {
get: createGetter(),
set: createSetter()
})
}
性能
上面readonly还有一个缺陷,就是每调用一次reactive都会创建一个get和set,但返回的都时同一个东西的很多个副本占用内存
// 解决:开始时调用createGetter/createSetter
const get = createGetter()
const set = createSetter()
// 更新后
function readonly (raw) {
return new Proxy(raw, {
get: createGetter(),
set: createSetter()
})
}
第二次重构
细化代码,还可以将Proxy的第二个参数抽离出来
// 新建baseHandlers.ts
const get = createGetter()
const set = createSetter()
// reactiveHandler
export const mutableHandles = {
get,
set
}
// 更新reactive
function reactive (raw) {
return new Proxy(raw, mutableHandles)
}
到这里优化完成,不过还可以优化让reactive可读性更高一些
function createActiveObject (raw, baseHandlers) {
return new Proxy(raw, baseHandlers)
}
// 更新reactive
function reactive (raw) {
return createActiveObject(raw, mutableHandles)
}
只写了reactive,readonly的优化逻辑与reactive相同
最终代码
baseHandles.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
}
}
// 抽离set
function createSetter () {
return function set (target, key, value) {
const res = Reflect.set(target, key, value)
trigger(target, key)
return res
}
}
export const mutableHandles = {
get,
set
}
export const readonlyHandles = {
get: readonlyGet,
set (target, key) {
console.warn(`key: ${key} set 失败,因为target是readonly`, target)
return true
}
}
reactive.ts
import { mutableHandles, readonlyHandles } from './baseHandle'
export function reactive (raw) {
return createActiveObject(raw, mutableHandles)
}
// 只读,意味着不能set
export function readonly (raw) {
return createActiveObject(raw, readonlyHandles)
}
function createActiveObject (raw: any, baseHandlers) {
return new Proxy(raw, baseHandlers)
}