likes
comments
collection
share

[Vue 源码] Vue 3.2 - toRef | toRefs | proxyRefs 原理

作者站长头像
站长
· 阅读数 13

运行结果

[Vue 源码] Vue 3.2  - toRef  | toRefs  | proxyRefs 原理

代码示例

  <script src="./dist/reactivity.global.js"></script>
  <body>
    <div id="app"></div>
    <script>
      let { ref, effect, reactive, toRef } = VueReactivity
      let obj = reactive({ flag: false })
      let state = toRef(obj, "flag")
      
      effect(() => {
        app.innerHTML = `ref is ${state.value} `
      })

      setTimeout(() => {
        state.value = true
      }, 2000)
    </script>

挂载阶段

第一:调用 toRef 函数,调用 new ObjectRefImpl 创建 objectRef 对象。

export function toRef<T extends object, K extends keyof T>(
  object: T,
  key: K,
  defaultValue?: T[K]
): ToRef<T[K]> {
  const val = object[key]
  return isRef(val)
    ? val
    : (new ObjectRefImpl(object, key, defaultValue) as any)
}

第二:调用 ObjectRefImpl 的 constructor 方法。初始化 object, key 属性。返回 objectRef 对象。

  constructor(
    private readonly _object: T,
    private readonly _key: K,
    private readonly _defaultValue?: T[K]
  ) {}

第三:再 effect 函数 执行 effct.fn 函数,也就是 () => { state.value = true }, 访问 state.value, 触发 objectRef 对象 value 属性的 get 操作。收集依赖到 obj 响应式对象的 flag 属性的依赖列表中去,返回属性值。

const targetMap = {
    {flag :false}: {flag: [ReactiveEffect]}
}
  get value() {
    const val = this._object[this._key]
    return val === undefined ? (this._defaultValue as T[K]) : val
  }

第四:初次挂载完成,渲染完毕

更新阶段

执行第一句代码: setTimeout(() => { state.value = true }, 2000)

触发 state objectRef 对象 value 属性的 setter 方法,实际上触发的是 reactive 响应式对象 obj 的 setter 操作。

  set value(newVal) {
    this._object[this._key] = newVal
  }

再 setter 操作中,通过 Reflect.set 方法修改响应式对象的值,再通过 trigger 操作。触发 flag 属性的依赖列表。再次执行 ReactiveEffect 对象的 fn 属性,拿到最新值,进行渲染。

更新完毕

toRefs 原理

    <script>
      let { ref, effect, toRefs, reactive } = VueReactivity
      let state = reactive({ num: 0, name: 'cyan' })
      const { num, name } = toRefs(state)

      effect(() => {
        app.innerHTML = `ref is ${num.value} ${name.value}`
      })

      setInterval(() => {
        num.value++
        name.value = 'mike'
      }, 2000)
    </script>

就是将对象的每一个属性,都调用 toRef 函数,转成 objectRef 对象。这样就可以进行解构,拿到每个 objectRef 对象,且仍然具有响应式。

export function toRefs<T extends object>(object: T): ToRefs<T> {
  if (__DEV__ && !isProxy(object)) {
    console.warn(`toRefs() expects a reactive object but received a plain one.`)
  }
  const ret: any = isArray(object) ? new Array(object.length) : {}
  for (const key in object) {
    ret[key] = toRef(object, key)
  }
  return ret
}

proxyRefs 原理

      let { ref, effect, toRefs, reactive, proxyRefs } = VueReactivity
      let state = reactive({ num: 0, name: 'cyan' })
      const p = proxyRefs(toRefs(state))

      effect(() => {
        app.innerHTML = `ref is ${p.num} ${p.name}`
      })

      setInterval(() => {
        p.num++
        p.name = 'mike'
      }, 2000)

proxyRefs 原理就是将 ref 对象做了一层 proxy 代理,访问 p.num 实际上是访问 p.num.value, 触发的仍然是 objectRef 对象 的 getter。 赋值p.num = "",实际上是 p.num.value = "", 触发的仍然是 objectRef 对象的 settter。


export function proxyRefs<T extends object>(
  objectWithRefs: T
): ShallowUnwrapRef<T> {
  return isReactive(objectWithRefs)
    ? objectWithRefs
    : new Proxy(objectWithRefs, shallowUnwrapHandlers)
}

const shallowUnwrapHandlers: ProxyHandler<any> = {
  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),
  set: (target, key, value, receiver) => {
    const oldValue = target[key]
    if (isRef(oldValue) && !isRef(value)) {
      oldValue.value = value
      return true
    } else {
      return Reflect.set(target, key, value, receiver)
    }
  }
}
转载自:https://juejin.cn/post/7209967260897755194
评论
请登录