Vue3.0 响应式系统(四) shallowReactive shallowReadonly

上一篇文章分析了reactive,readonly方法,这两种方法都是对target进行深度侦测(虽然是延迟侦测),Vue3.0还给我们提供了对target只进行第一层侦测,那就是shallowReactive,shallowReadonly方法。我们这篇文章看看这两个方法是怎么实现的。

先看个例子:

​
<html>
  <head>
  </head>
  <body>
    <div id="app">
      <div>student class: {{state.student.name}}</div>
    </div>
    <script src="../dist/vue.global.js"></script>
    <script>
      const {createApp, reactive, shallowReactive, readonly, shallowReadonly} = Vue
      const app = createApp({
        setup(){       
          let obj = {
            msg: 'hello',
            student: {
              name: 'xiaoliu',
              age: 20
            }
          }
          
          let state = shallowReactive(obj)    
          return {state}
        }
      }).mount('#app')      
      setTimeout(() => {
        console.log(`name is: ${app.state.student.name}`)
        app.state.student.name = 'xiaowang'      
        console.log(`name after is: ${app.state.student.name}`)
      }, 2000)
    </script>
  </body>
</html>

shallowReactive(obj)后更改student.name后虽然属性的值已经被更改了,但页面并没有更新。我们再看看shallowReadonly的例子:

​
<html>
  <head>
  </head>
  <body>
    <div id="app">
      <div>student class: {{state.student.name}}</div>
    </div>
    <script src="../dist/vue.global.js"></script>
    <script>
      const {createApp, reactive, shallowReactive, readonly, shallowReadonly} = Vue
      const app = createApp({
        setup(){       
          let obj = {
            msg: 'hello',
            student: {
              name: 'xiaoliu',
              age: 20
            }
          }
          
          let state = shallowReadonly(obj)    
          return {state}
        }
      }).mount('#app')      
      setTimeout(() => {
        console.log(`name is: ${app.state.student.name}, msg is: ${app.state.msg}`)
        app.state.student.name = 'xiaowang'      
        app.state.msg = 'world'
        console.log(`name after is: ${app.state.student.name}, msg is: ${app.state.msg}`)

      }, 2000)
    </script>
  </body>
</html>

打印如下:

通过打印看出,不可以修改第一层属性,但可以修改除了第一层属性之外的深层次属性。修改第一层属性,在开发环境下给了个警告。

下面我们具体看看Vue3.0如何做到这一点的。

export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
    return target
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers
  )
}

/**
 * Return a shallowly-reactive copy of the original object, where only the root
 * level properties are reactive. It also does not auto-unwrap refs (even at the
 * root level).
 */
export function shallowReactive<T extends object>(target: T): T {
  return createReactiveObject(
    target,
    false,
    shallowReactiveHandlers,
    shallowCollectionHandlers
  )
}

对比reactive 和 shallowReactive,只是传给createReactiveObject方法的最后两个参数不同。这两个参数是用来传给new Proxy的第二个参数,我们接着往下看,

const get = /*#__PURE__*/ createGetter()
const shallowGet = /*#__PURE__*/ createGetter(false, true)

const set = /*#__PURE__*/ createSetter()
const shallowSet = /*#__PURE__*/ createSetter(true)


export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}

export const shallowReactiveHandlers: ProxyHandler<object> = extend(
  {},
  mutableHandlers,
  {
    get: shallowGet,
    set: shallowSet
  }
)

shallowReactiveHandlers对象覆盖了mutableHandlers对象的get,set方法,其他方法和mutableHandlers一样。且shallowGet 也调用的是createGetter方法,只是传递的参数不一样。shallowSet采用了同样的方式处理。所以重点看下createGetter,createSetter这两个方法。

function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    if (key === ReactiveFlags.IS_REACTIVE) {
      //isReactive方法会走到这里
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      //isReadonly方法会走到这里
      return isReadonly
    } else if (
      //toRaw方法会走这里
      key === ReactiveFlags.RAW &&
      receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)
    ) {
      return target
    }

    const targetIsArray = isArray(target)

    //这里没怎么看懂
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }

    const res = Reflect.get(target, key, receiver)

    //这里也没怎么看懂
    if (
      isSymbol(key)
        ? builtInSymbols.has(key as symbol)
        : isNonTrackableKeys(key)
    ) {
      return res  
    }

    //这里是关键,如果不是readonly的话,就进行track,也就是进行收集依赖
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }

    //如果是shallowReactive,或shallowReadonly,则直接就return出去,不进行深度侦测
    if (shallow) {
      return res
    }

    //这里也没怎么看懂,先过去,之后再仔细研究
    if (isRef(res)) {
      // ref unwrapping - does not apply for Array + integer key.
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res
    }

    //这里是关键,如果不是shallow的,则判断res是否为对象,如果是对象的话,再递归进行reactive或readonly
    if (isObject(res)) {
      // Convert returned value into a proxy as well. we do the isObject check
      // here to avoid invalid value warning. Also need to lazy access readonly
      // and reactive here to avoid circular dependency.
      return isReadonly ? readonly(res) : reactive(res)
    }
    //最后return res
    return res
  }
}

createGetter方法其实是对get方法的封装,这样在一个方法中就可以兼容处理reactive,readonly了。get方法主要是做了3件事,第一件是进行了依赖收集。第二件是根据参数判断是否进行深度侦测,如果是对象则进行递归进行侦测。第三件是判断isReactive,isReadonly。

下面看看createSetter方法,

function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    const oldValue = (target as any)[key]
    if (!shallow) {
      //这里没怎么看懂,之后再仔细分析
      value = toRaw(value)
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        oldValue.value = value
        return true
      }
    } else {
      // in shallow mode, objects are set as-is regardless of reactive or not
    }
    //这里是关键,用来判断target是否有key属性
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    const result = Reflect.set(target, key, value, receiver)
    // don't trigger if target is something up in the prototype chain of original
    
    if (target === toRaw(receiver)) {
      //不管是新增属性,还是更改属性值都要进行trigger,这样页面才能更新
      if (!hadKey) {
        //如果没有key属性,说明是新增
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        //如果有key属性,说明是更改属性值
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}

createSetter方法也对set方法进行了封装,当新增属性或更改属性值的时候进行了trigger,从而更新页面。

最后我们顺便看下,isReactive,isReadonly,isProxy方法


export function isReactive(value: unknown): boolean {
  if (isReadonly(value)) {
    return isReactive((value as Target)[ReactiveFlags.RAW])
  }
  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
}

export function isReadonly(value: unknown): boolean {
  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}

export function isProxy(value: unknown): boolean {
  return isReactive(value) || isReadonly(value)
}

如果value已经被reactive,则!!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])  会触发get函数,get函数中会判断key是否为ReactiveFlags.IS_REACTIVE,如果是的话就返回true。isReadonly,处理过程是一样的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值