vue3 - mini-vue(三)实现readonly,reactive、readonly代码重构

101 阅读1分钟

这一篇主要实现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)
}