读Vue3源码之reactive

111 阅读3分钟

前提

响应式API reactive 是放在了 reactivity 文件夹里面,这里面还同样放置了ref,

ref 要另起一篇来讲

reactive

reactive.ts

总体来说,做了两件事,

  1. 把对象转化为响应式对象
  2. 收集依赖,更新依赖

createReactiveObject 函数

  • reactive 核心函数
  • createReactiveObject 函数核心就是Proxy
  • 目的是可以监听到用户的get 和 set的动作
  • 使用缓存做了优化
  • 参数proxyMap 其实就是reactiveMap, 用来做缓存的
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  
  /* 如果不是对象的话,直接返回,因为基础类型是要用ref函数处理的 */
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // target 已经是一个proxy对象了,直接返回
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  /* 
    先获取,是否缓存过 target
    为了解决: 一个对象多次使用reactive转化,都是返回同一个响应式对象
  */
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    /* 如果之前缓存过,那么直接返回 */
    return existingProxy
  }
  /* 
      获取目标类型,用于判断下面的逻辑
  */
  const targetType = getTargetType(target)
  /* 如果是冻结对象,也就是说不可扩展的对象,就直接返回target */
  if (targetType === TargetType.INVALID) {
    return target
  }
  /* 
    这里是这个函数的关键代码
    通过proxy代理对象
    collectionHandlers 这个指的是 Map, Set, WeakMap, WeakSet 这四种类型的 handlers
    baseHandlers: Object, Array
  */
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  /* 
    代理完之后,把proxy给缓存起来 
  */
  proxyMap.set(target, proxy)
  // 最后,返回proxy对象
  return proxy
}

mutableHandlers

/* 
  这是最基础的 handlers
  处理对象和数组
*/
export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}

createGetter

createGetter 生成get 函数

/* 
  createGetter 接受两个参数
  1,是否只读,2, 浅代理
*/
function createGetter(isReadonly = false, shallow = false) {
  /*  */
  return function get(target: Target, key: string | symbol, receiver: object) {
    /* 下面的判断都是返回特定的get */
    if (key === ReactiveFlags.IS_REACTIVE) {
      /* 访问属性 __v_isReactive */
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      /* 
       访问属性  __v_isReadonly
      */
      return isReadonly
    } else if (
      key === ReactiveFlags.RAW &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
          ? shallowReactiveMap
          : reactiveMap
        ).get(target)
    ) {
      /* toRaw() 函数, 执行到这里了 */
      return target
    }

    /* 判断是不是数组 */
    const targetIsArray = isArray(target)
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      /* 
        数组的话,到这里结束了
        在这个里面 arrayInstrumentations, 重点看一下这个,处理了数组的一些方法
        数组其实需要单独开一篇讲解的,可以参考字节的大佬的文章,https://juejin.cn/post/6844904056356339720
       */
      return Reflect.get(arrayInstrumentations, key, receiver)
    }
    
    /* 
      获取get返回值
    */
    const res = Reflect.get(target, key, receiver)
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }
    /* 
       问题:为什么是 readonly 的时候不做依赖收集呢
     readonly 的话,是不可以被 set 的, 那不可以被 set 就意味着不会触发 trigger
     所有就没有收集依赖的必要了
     只读的数据回改变,也就不需要收集依赖
    */
    if (!isReadonly) {
      /* 
          在触发 get 的时候进行依赖收集 
      */
      track(target, TrackOpTypes.GET, key)
    }
    /* shallow 的话,只劫持一层 */
    if (shallow) {
      /* 直接return */
      return res
    }
    /* 如果是ref 类型 */
    if (isRef(res)) {
      // ref unwrapping - does not apply for Array + integer key.
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res
    }

    if (isObject(res)) {
      /* 
        如果是对象的话
        再执行reactive代理,每一层都代理
        如果是只读的话,就直接返回 readonly后的
        注意: 这里是延迟代理
        这里注意,只会代理第一层的数据,只有在读取数据触发get 才会把嵌套的对象转化成响应式对象
        Vue2里面,是直接递归把所有的数据全部转化成响应式对象
       */
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}

createSetter

创建 set 函数

function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    /* 旧值 */
    let oldValue = (target as any)[key]

    if (!shallow) {
      /* 不是浅代理 */
      /* 获取本身的值,不是响应式的值 */
      value = toRaw(value)
      oldValue = toRaw(oldValue)
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        /* 
          不是数组,并且,旧值是ref, 新值不是ref,执行下面的操作
        */
        oldValue.value = value
        return true
      }
    } else {
      // in shallow mode, objects are set as-is regardless of reactive or not
    }
    /* 判断要设置的key 存不存在 */
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)

    const result = Reflect.set(target, key, value, receiver)
    // receiver : Proxy或者继承Proxy的对象
    /*  
      if 用于判断不是继承自proxy的对象,也就是说,如果是原型链中的东西,就不要触发
    */
    if (target === toRaw(receiver)) {
      /* 触发依赖 */
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {

        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}