Vue3源码解读-响应式(3)

66 阅读2分钟

baseHandler

reactivity/src/baseHandlers.ts

getter

创建getter函数, 用在proxy的handlers中, 获取到的值中, 如果是非shadowReactive且非基本类型的, 此时转成reactive后返回, 让它可以继续触发响应.

/** 创建getter函数,  */
function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    // 获取flag值
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (key === ReactiveFlags.IS_SHALLOW) {
      return shallow
    } else if (
      key === ReactiveFlags.RAW &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
          ? shallowReactiveMap
          : reactiveMap
        ).get(target)
    ) {
      return target
    }

    const targetIsArray = isArray(target)

    // 获取array的原有属性, indexOf, includes等
    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) : isNonTrackableKeys(key)) {
      return res
    }

    // 如果不是只读, 此时触发收集
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }

    // 浅层直接返回
    if (shallow) {
      return res
    }

    // 非浅层, 自动解包ref, 这里不需要再转一次reactive是因为如果value是object的情况下, 默认会转成reactive
    if (isRef(res)) {
      return targetIsArray && isIntegerKey(key) ? res : res.value
    }

    // 如果不是readonly, 则此时用reactive包装.
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}

setter

创建setter函数, 用于proxy的handlers中, 先缓存旧值, 然后判断旧值是否只读等, 如果否, 再判断旧值是否等于新值, 如果不相等, 则触发回调.

/** 创建setter函数 */
function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    let oldValue = (target as any)[key]
    if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
      return false
    }
    if (!shallow) {
      if (!isShallow(value) && !isReadonly(value)) {
        oldValue = toRaw(oldValue)
        value = toRaw(value)
      }
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        // 如果oldValue是ref, 不在此处触发回调.
        oldValue.value = value
        return true
      }
    } else {
      // in shallow mode, objects are set as-is regardless of reactive or not
    }

    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)) {
      // 触发回调
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}

delete, has, ownKeys

其他的触发函数

/** 拦截删除操作, 触发watch */
function deleteProperty(target: object, key: string | symbol): boolean {
  const hadKey = hasOwn(target, key)
  const oldValue = (target as any)[key]
  const result = Reflect.deleteProperty(target, key)
  if (result && hadKey) {
    trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
  }
  return result
}

/** 拦截has操作, 触发依赖收集 */
function has(target: object, key: string | symbol): boolean {
  const result = Reflect.has(target, key)
  if (!isSymbol(key) || !builtInSymbols.has(key)) {
    track(target, TrackOpTypes.HAS, key)
  }
  return result
}

function ownKeys(target: object): (string | symbol)[] {
  track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY)
  return Reflect.ownKeys(target)
}

总结

  1. 使用ref, computed, reactive时, 会分别用class的getter和setter函数, 或者使用proxy的方式进行劫持, 如果变量是基础类型, 则使用ref的方式, 如果变量是对象类型, 则使用proxy的方式进行, 在proxy的方式中, 获取到的新值如果也是对象, 则此时也会转为proxy的方式返回.
  2. 在获取变量值时, 会调用track函数进行依赖收集, 此时收集到的依赖保存为targetMap[target][key]中, 另外同时保存在activeEffect!.deps
  3. 在触发setter函数后, 调用trigger函数触发重新计算computedeffect, 此时从targetMap[target][key]中取出对应的deps列表, 并且分不同类型进行触发操作, 最后调用的是effect.scheduler() 或者effect.run().