vue3源码系列之高清版reactive源码分析篇

vue3已经出来很久了,相信很多小伙伴也在项目中使用了,但是其内部原理如何???又有哪些细节进行处理了,今天我们来一起说道说道

  • 会分析下reactive响应式模块
  • 在使用缓存策略上有什么技巧
  • 为什么说reactive 是懒代理,而不是递归代理

1. 先看一段基本使用

demo地址:点击这里

      const { reactive, effect } = Vue
      const target = {
        name: 'lixx',
        age: 27
      }
      const state = reactive(target)
      const state1 = reactive(target)
      const state2 = reactive(state1)
      console.log(state === state1, state1 === state2) // true true

      effect(() => {
        document.getElementById('app').innerHTML = `我的名字是:${state.name}, 我${state.age}岁了`
      })

      setTimeout(() => {
        state.age = 28
      }, 4000)
  • 模块reactive是将对象变换为响应式
  • 模块effect可以理解为依赖追踪,就是函数内执行依赖发生变化,会自动执行函数,初期的时候会执行一次
  • 上述代码中state === state1, state1 === state2是为了引出reactive的缓存技巧

2. 实现一个简单的

手写内容包括:代理,缓存,懒代理

      // 源码实现部分 -----------------------------------------------------------
      // 进行reactive缓存
      const reactiveMap = new WeakMap()
      const isObject = (target) => target && typeof target === 'object'
      const ReactiveFlags = {
        IS_REACTIVE: '__v_isReactive'
      }

      const reactiveImpl = (target) => {
        if (!isObject(target)) {
          console.warn(`${target} need is object`)
          return
        }

        // 如果传递的是源对象,源对象被代理过后,直接返回代理后的对象
        // 这个缓存是 当target是代理前对象时
        const existingProxy = reactiveMap.get(target)
        if (existingProxy) return existingProxy

        // 这个缓存时 当target是代理后对象时
        if (target && target[ReactiveFlags.IS_REACTIVE]) return target

        const proxy = new Proxy(target, {
          get: (target, key, receiver) => {
            if (key === ReactiveFlags.IS_REACTIVE) return true

            const res = Reflect.get(target, key, receiver)
            // 如果是对象,继续代理,然后返回
            return isObject(res) ? reactive(res) : res
          },
          set: (target, key, value, receiver) => {
            Reflect.set(target, key, value, receiver)
          }
        })
        reactiveMap.set(target, proxy)
        return proxy
      }

      const Vue = { reactive: reactiveImpl }

      // 使用部分 -------------------------------------------------------------------
      const { reactive } = Vue
      const target = {
        name: 'lixx',
        age: 27
      }
      const state = reactive(target)
      const state1 = reactive(target)
      const state2 = reactive(state1)
      console.log(state === state1, state1 === state2) // true true

      document.getElementById('app').innerHTML = `我的名字是:${state.name}, 我${state.age}岁了`
  • 上述是reactive的核心部分,但是有几个点需要注意:
    • 如果传递给函数reactive的参数是源对象怎么办???
    • 如果传递给函数reactive的参数是代理后的对象怎么办???
    • 如果代理的对象是深层嵌套怎么办???
  • 根据上述的问题,通过我们手写实现来一一解答:
    • 问题1:如果传递给函数reactive的参数是源对象怎么办???
      • 如果传递的对象是代理前的对象,在源码中会调用proxy来进行代理
      • 将代理后的数据存放到reactiveMap.set(target, proxy)
      • 如果下次重复代理的时候,直接返回。可以通过上述代码state === state1 // true来体现出来
    • 问题2:如果传递给函数reactive的参数是代理后的对象怎么办???
      • 这里有个疑问:如果让我们自己来判断对象是否已经代理过,我们应该怎么判断呢?
        • 直观的的办法:给proxy添加一个变量,判断变量是否存在。这种办法是可行的,但是如果我们不想让用户知道有这个变量怎么办呢,还有别的办法,那接下来看下Vue是如何做的
      • 通过target && target[ReactiveFlags.IS_REACTIVE] 来判断是否代理过。执行此代码时,如果target是代理后对象,那么一定会触发proxy.get函数的,我们就可以通过代码if (key === ReactiveFlags.IS_REACTIVE) return true来告诉用户变量被代理过
    • 问题3:如果代理的对象是深层嵌套怎么办???
      • Vue中使用了懒代理的模式来解决嵌套的问题。Vue3Vue2的区别之一就是:Vue2遇到迭代对象是递归重写的。但是Vue3是懒代理的
      • 通过代码return isObject(res) ? reactive(res) : res 来实现。

3. 源码细节扫描

3.1 reactive只能传递对象

地址:packages\reactivity\src\reactive.ts,函数createReactiveObject

  // 传递的属性 必须是对象
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
3.2 如果被代理过了直接返回

地址:packages\reactivity\src\reactive.ts,函数createReactiveObject

	// 此时传递对象是代理后对象
  // 如果被代理过直接返回 主要是【target[ReactiveFlags.IS_REACTIVE]】起了作用
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }

	// 重复代理,多次传递同一个对象进行代理
  // 判断缓存中是否有值 进行返回
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
3.3 如果传递的target是数组单独处理

地址:packages\reactivity\src\baseHandlers.ts,函数createGetter

    // 判断是否是数组
    const targetIsArray = isArray(target)
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }
3.4 如果不是只读,收集依赖

地址:packages\reactivity\src\baseHandlers.ts,函数createGetter

    // 如果不是只读, 触发依赖收集
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }
3.5 如果是浅代理,代理一层后直接返回

地址:packages\reactivity\src\baseHandlers.ts,函数createGetter

    // 如果是浅代理 直接返回
    if (shallow) {
      return res
    }
3.6 如果是ref,直接调用.value进行返回

地址:packages\reactivity\src\baseHandlers.ts,函数createGetter

    // 如果代理的ref 直接使用【.value】进行返回
    if (isRef(res)) {
      // ref unwrapping - skip unwrap for Array + integer key.
      return targetIsArray && isIntegerKey(key) ? res : res.value
    }
3.7 如果是深层对象,进行懒代理

地址:packages\reactivity\src\baseHandlers.ts,函数createGetter

    // 如果返回的值对象 进行懒代理 然后返回
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res)
    }

结束

上述就是针对reactive进行分析,以及手写部分。可以参照地址https://gitee.com/li_haohao_1/vue-world/tree/master/vue3/reactive。如果觉得分享有用处的话,欢迎点赞,收藏,关注一条龙服务哦

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值