Vue3.0源码解析「reactive」篇 — 5.baseHandlers

444 阅读5分钟

mutableHandlers

mutableHandlers 是非集合对象的普通响应式代理处理函数,来看看代码:

export const mutableHandlers: ProxyHandler<object> = {
  get, // 用于拦截对象的读取属性操作
  set, // 用于拦截对象的设置属性操作
  deleteProperty, // 用于拦截对象的删除属性操作
  has, // 检查一个对象是否拥有某个属性
  ownKeys // 针对 getOwnPropertyNames,  getOwnPropertySymbols, keys 的代理方法
}

Set handler

set 拦截器方法通过 createSetter 创建,这个函数首先判断是否为浅层代理模式,深层代理需要将嵌套的对象也转化为响应式代理,浅层则保持原值:

  • 如果是深层代理则调用 toRaw 获取 valueoldValue 的原始值,便于在后序的 trigger 操作中使用。

之后 set 拦截器中主要执行的就是 trigger 操作,触发属性改变带来的副作用。对于触发 trigger 操作的类型 vue 做了如下的判断:

  • 对象原本就包含此属性,并且对于旧值来说 hasChanged,则触发 TriggerOpTypes.SET
  • 如果对象本不包含此属性,则触发 TriggerOpTypes.ADD
const set = /*#__PURE__*/ createSetter()
/**
 * @description: 拦截对象的设置属性操作 
 * @param {shallow} 是否是浅观察 
 */
function createSetter(shallow = false) {
  /**
   * @description: 
   * @param {target} 目标对象
   * @param {key} 设置的属性的名称
   * @param {value} 要改变的属性值 
   * @param {receiver} 如果遇到 setter,receiver则为setter调用时的this值 
   */
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    const oldValue = (target as any)[key]

    // 如果模式不是浅观察
    if (!shallow) {
      value = toRaw(value)
      oldValue = toRaw(oldValue)
      // 并且目标对象不是数组,旧值是ref,新值不是ref,则直接旧的 oldRef.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
    }

    // 检查对象是否有这个属性并赋值
    const hadKey = 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 ADD
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        // 存在则trigger SET
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}

toRaw

重点讲一下 toRaw 方法,toRaw 用于返回对象的原始值,如果 observed 是一个响应式代理对象,那就通过 ReactiveFlags.RAW 拿到其原始值,否则返回其本身:

export function toRaw<T>(observed: T): T {
  return (
    (observed && toRaw((observed as Target)[ReactiveFlags.RAW])) || observed
    // a && b; if both a and b are truthy, it returns b;
    // a || b; if only b is truthy, it returns b;
  )
}

Get Handler

Get 拦截器的创建方式和 set 相同,函数流程如下:

如果|_GET_|操作获取的是 ReactiveFlags.IS_REACTIVE|IS_READONLY|RAW,处理逻辑为:

  • ReactiveFlags.IS_REACTIVE!isReadonly 返回响应式代理是否为普通响应式代理。
  • ReactiveFlags.IS_READONLY:返回响应式代理是否为只读响应式代理。
  • RAW:返回响应式代理的原始值,注意 target 源对象就是在这里被缓存的。

之后如果目标对象是数组并且 key 属于三个方法之一 ['includes', 'indexOf', 'lastIndexOf'],走 arrayInstrumentations 的处理流程。

排除掉属性为内置方法|属性,原型对象方法|属性之后,如果不是只读代理那就触发 track 操作记录副作用。

最后判断是否为浅代理来决定是直接返回,还是返回嵌套对象的只读|普通响应式代理

const get = /*#__PURE__*/ createGetter()
function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    //  ReactiveFlags 是在reactive中声明的枚举值,如果key是枚举值则直接返回对应的布尔值
    if (key === ReactiveFlags.IS_REACTIVE) return !isReadonly
		else if (key === ReactiveFlags.IS_READONLY) return isReadonly
		else if (key === ReactiveFlags.RAW) return target

    const targetIsArray = isArray(target)
    
		// 如果目标对象是数组并且 key 属于三个方法之一 ['includes', 'indexOf', 'lastIndexOf'],即触发了这三个操作之一
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }

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

    // 如果 key 是 symbol 内置方法,或者访问的是原型对象,直接返回结果,不收集依赖;
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }
		
		// 如果非只读,则通过 track 函数设置副作用跟踪;
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }
	
    // 如果是浅层代理模式则直接返回 GET 操作的结果;
    if (shallow) {
      return res
    }

    if (isRef(res)) {
      // 如果get的结果是ref,通过 shouldUnwrap 判断是否需要解包;
      // 这里也明确规定了只有对象的某些属性会被解包,数组以及对index属性则不会解包;
      // ref unwrapping - does not apply for Array + integer key.
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res
    }
		
    // 由于 proxy 初始 set 的时候可能只代理了一层,所以 target[key] 的值如果是对象,就继续对其设置代理
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}

Has Handler

has 代理比较简单,单纯的判断一下是否为内置属性|Symbol 属性,然后触发 TrackOpTypes.HAS 类型的 track 操作记录副作用。

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
}

DeleteProperty Handler

删除属性的操作执行成功之后会触发 TriggerOpTypes.DELETE 类型的 trigger 操作:

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
}

OwnKeys Handler

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

otherHandlers

至于其他的 getter|setter 只是做了一些调参:

const shallowGet = /*#__PURE__*/ createGetter(false, true)
const readonlyGet = /*#__PURE__*/ createGetter(true)
const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true)
const shallowSet = /*#__PURE__*/ createSetter(true)

readonly

readonlyHandlers 禁止删除和修改:

export const readonlyHandlers: ProxyHandler<object> = {
  get: readonlyGet,
  set(target, key) {
    if (__DEV__) {
      console.warn(
        `Set operation on key "${String(key)}" failed: target is readonly.`,
        target
      )
    }
    return true
  },
  deleteProperty(target, key) {
    if (__DEV__) {
      console.warn(
        `Delete operation on key "${String(key)}" failed: target is readonly.`,
        target
      )
    }
    return true
  }
}

shallowReactiveHandlers

gettersetter 中阻止嵌套下响应式:

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

shallowReadonlyHandlers

上面两者的结合

// Props handlers are special in the sense that it should not unwrap top-level
// refs (in order to allow refs to be explicitly passed down), but should
// retain the reactivity of the normal readonly object.
export const shallowReadonlyHandlers: ProxyHandler<object> = extend(
  {},
  readonlyHandlers,
  {
    get: shallowReadonlyGet
  }
)