Vue3.0 : 基于Proxy实现的Reactivity 系统构建一个RGB调色板

417 阅读2分钟

HTML部分

    <section style="display: flex;flex-direction: column;max-width: 200px;">
      <label for="r">r
        <input type="range" id="r" min="0" max="255">
      </label>
      <label for="g">g
        <input type="range" id="g" min="0" max="255">
      </label>
      <label for="b">b
        <input type="range" id="b" min="0" max="255">
      </label>
      <div id="color" style="width: 100px;height: 100px;background-color: rgb(0, 0, 0);margin-top: 24px;"></div>
    </section>

JavaScript部分

let obj = {
    r: 0,
    g: 0,
    b: 0
  }

  let usedReactivities = [], // 依赖收集
    handlers = new Map(), // handler 集合 
    reactivities = new Map(); // reactivity 集合

  // reactive system
  function reactive(obj) {
    // 复用已有 reactivity
    if (reactivities.has(obj)) return reactivities.get(obj)

    let proxy = new Proxy(obj, {
      get(obj, prop) {
        // console.log("get :", obj, prop)
        // 收集依赖
        usedReactivities.push([obj, prop])
        if (typeof obj[prop] === "object")
          return reactive(obj[prop])
        return obj[prop]
      },
      set(obj, prop, val) {
        obj[prop] = val
        // 执行对应 obj & prop 下的 handler
        if (handlers.get(obj))
          if (handlers.get(obj).get(prop))
            for (const handler of handlers.get(obj).get(prop))
              handler()

        // console.log("set :", obj, prop, val)
        return obj[prop]
      }
    })

    // 存储进入 reactivities 集合
    reactivities.set(obj, proxy)

    return proxy
  }

  // effect system
  function effect(handler) {
    // 初始化依赖收集
    usedReactivities = []
    // 执行 handler 相当于初始化
    handler()
    // 设置 handler 进入handlers
    for (let [obj, prop] of usedReactivities) {
      if (!handlers.has(obj)) handlers.set(obj, new Map())
      if (!handlers.get(obj).has(prop)) handlers.get(obj).set(prop, [])

      handlers.get(obj).get(prop).push(handler)
    }

  }

  let p = reactive(obj)

  effect(() => document.getElementById("r").value = p.r)
  effect(() => document.getElementById("g").value = p.g)
  effect(() => document.getElementById("b").value = p.b)
  effect(() => document.getElementById("color").style.backgroundColor = `rgb(${p.r}, ${p.g}, ${p.b})`)


  document.getElementById("r").addEventListener("input", e => p.r = e.target.value)
  document.getElementById("g").addEventListener("input", e => p.g = e.target.value)
  document.getElementById("b").addEventListener("input", e => p.b = e.target.value)
  // document.getElementById("color").addEventListener("input", e => console.log(e))

几个关键点

依赖收集与初始化

1、收集:在每次prop获取时收集对应的[obj, prop]存入usedReactivities
2、在effect中先初始化usedReactivities
3、在effect中立即调用handler(此时一般会调用get方法 依赖收集👌)
4、基于usedReactivities 遍历 其handler 并且将所有的handler存入 handlers 哈希表中 便于在set时取出依赖调用

多层嵌套属性 复用Reactivity obj(递归调用) 在reactive方法中

1、先在 reactivities 哈希表中 找是否存在 同样的obj 若有则直接返回
2、在get过程中若值为obj则递归调用 reactive
3、set时 执行对应obj prop下的所有handler (同一个porp可以挂载多个handler)