【真香系列】Vue-Next 源码第五章

1,405 阅读5分钟

目录

Demo

回顾下我们的案例,我们使用 reactive 创建了一个 state,之后该 state 的变化都会触发响应式的更新渲染。

const state = reactive({ message: 'World' })

reactive

调用 createReactiveObject 方法并返回创建的 state 代理对象。

// packages/reactivity/src/reactive.ts
export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  // 如果试图观测一个只读的代理对象,返回只读版本
  // Vue3 中有一个 readonly api 可以创建只读的代理对象,如果操作只读代理会触发一个 warning
  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
    return target
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers
  )
}

createReactiveObject

先看下参数:

  1. target{ message: 'World' } 对象
  2. isReadonly 用来区分是否为只读,Vue3 中可以使用 readonly 创建只读的代理对象。
  3. baseHandlers 针对 Object/Arrayproxy 相关属性 get/set/has/ownKeys/deleteProperty
  4. collectionHandlers 针对集合类型 Set/Map/WeakMap/WeakSetproxy 相关属性

packages/reactivity/src/baseHandlers.ts

packages/reactivity/src/collectionHandlers.ts

然后经过一系列如是否可创建、是否已经是 Proxy 等判断后,创建 target 对应 Proxy

// packages/reactivity/src/reactive.ts
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>
) {
  // 必须是对象才可以被创建 reactive
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // target is already a Proxy, return it.
  // exception: calling readonly() on a reactive object
  // 如果目标已经是一个 Proxy 对象则返回
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // target already has corresponding Proxy
  // 在 readonlyMap/reactiveMap 中寻找与 target 对应的 Proxy,如果已经存则返回这个 Proxy 对象
  const proxyMap = isReadonly ? readonlyMap : reactiveMap
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only a whitelist of value types can be observed.
  // 进一步判断目标上是否存在 __v_skip 属性或者目标是一个不可扩展的对象(如 Object.freeze 会把对象标记为不可扩展)
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  
  // 创建 Proxy 对象,Object/Array 使用 baseHandlers,集合使用 collectionHandlers
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  
  // 放入 proxyMap 缓存避免重复创建
  proxyMap.set(target, proxy)
  return proxy
}

ReactiveFlags

markRawVue3 提供的一个方法,可以让一个对象不可被转换为 Proxy 对象,调用这个方法会在对象上添加一个 __v_skip 标识属性。__v_isReactive__v_isReadonly 用来区分响应或只读。__v_raw 原始 target

// packages/reactivity/src/reactive.ts
export const enum ReactiveFlags {
  SKIP = '__v_skip',
  IS_REACTIVE = '__v_isReactive',
  IS_READONLY = '__v_isReadonly',
  RAW = '__v_raw'
}

TargetType

根据 TargetType 会使用对应的 Handlers

// packages/reactivity/src/reactive.ts
const enum TargetType {
  INVALID = 0,
  COMMON = 1, // Object/Array
  COLLECTION = 2 // Map/Set/WeakMap/WeakSet
}

function targetTypeMap(rawType: string) {
  switch (rawType) {
    case 'Object':
    case 'Array':
      return TargetType.COMMON
    case 'Map':
    case 'Set':
    case 'WeakMap':
    case 'WeakSet':
      return TargetType.COLLECTION
    default:
      return TargetType.INVALID
  }
}

shallowReactive & shallowReadonly

Vue3 可以创建四种类型的 Proxy 对象,分别为:

  1. reactive
  2. shallowReactive 只对根属性做响应处理
  3. readonly
  4. shallowReadonly 只对根属性做只读处理

shallow 前缀的代表响应的处理,每种类型都有对应的 Proxy Handler

baseHandlers & collectionHandlers

baseHandlers 包括 mutableHandlersreadonlyHandlersshallowReactiveHandlersshallowReadonlyHandlers

// packages/reactivity/src/baseHandlers.ts
export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}

这里我们详细看 mutableHandlers,主要介绍 proxy 的两个属性:

get

// packages/reactivity/src/baseHandlers.ts
function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (
      key === ReactiveFlags.RAW &&
      receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)
    ) {
      return target
    }

    const targetIsArray = isArray(target)
    if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }

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

    const keyIsSymbol = isSymbol(key)
    if (
      keyIsSymbol
        ? builtInSymbols.has(key as symbol)
        : key === `__proto__` || key === `__v_isRef`
    ) {
      return res
    }
	
    // 非只读进行 track
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }
	
    // shallowReactiveHandlers 用
    if (shallow) {
      return res
    }
	
    // 如果是 res 是一个 ref,判断是否为数组,非数组进行 unwrapped
    if (isRef(res)) {
      // ref unwrapping - does not apply for Array + integer key.
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res
    }
		
    // 如果 res 是对象,则进行 readonly/reactive
    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
  }
}

当对 stateget 操作时,先判断 state 是否为数组且调用方法 key 是否为 includes/indexOf/lastIndexOf,如果是则使用 arrayInstrumentations 中对应的方法。arrayInstrumentations 包装了数组的各个方法。

// packages/reactivity/src/baseHandlers.ts
;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
  const method = Array.prototype[key] as any
  arrayInstrumentations[key] = function(this: unknown[], ...args: unknown[]) {
    const arr = toRaw(this) // 原始数组
    // 因为 includes/indexOf/lastIndexOf 同样会触发数组每一项的 getter
    // 数组的每一项都要进行 track 依赖收集,因为后续数组操作的都是原始对象而非 proxy,不能自动收集
    for (let i = 0, l = this.length; i < l; i++) {
      track(arr, TrackOpTypes.GET, i + '')
    }
    // we run the method using the original args first (which may be reactive)
    // 先用初始值执行一次(初始值可能是 reactive)
    const res = method.apply(arr, args)
    if (res === -1 || res === false) {
      // if that didn't work, run it again using raw values.
      // 如果不成功,初始值转成原始值执行
      // 之所以会执行两次是因为,如果原始数组 raw 里包含一个 reactive obj, 原始数组在转成 reactive arr, 那么 arr 其实是不包含 obj 的,是转换之前的 raw 包含 obj
      return method.apply(arr, args.map(toRaw))
    } else {
      return res
    }
  }
})

如果操作的不是数组,则 const res = Reflect.get(target, key, receiver) 获取值,如果不是只读,则进行 track 依赖收集。

set

// packages/reactivity/src/baseHandlers.ts
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)
      // 如果 oldValue 是一个 ref,要改变的是 unwrapper 的 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,决定是添加还是 set 属性
    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
    // 如果 target 不等于 receiver 证明 setter 操作的属性是在原型链上,那么不进行 trigger
    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
  }
}

第四篇文章里介绍了更新流程,当时介绍的入口方法是 trigger,其实就是从这里触发的。

reactive 总结

  1. proxy 大致分为四种,每种都有对应的 handlers
  2. handlers 重点在于 track/trigger 操作,分别代表依赖收集和派发更新。
  3. Vue3 对于数组方法进行了 hack

接下来我们重点介绍 effect

reactive 脑图