Vue3源码阅读笔记——响应式reactivity部分II:reactive,ref等

Posted by Mars . Modified at

尝试探索Vue3源码: 响应式reactivity部分: reactive,ref等。

原项目目录: /packages/reactivity

1. reactive()函数

/*
 * Creates a reactive copy of the original object.
 *
 * The reactive conversion is "deep"—it affects all nested properties. In the
 * ES2015 Proxy based implementation, the returned proxy is **not** equal to the
 * original object. It is recommended to work exclusively with the reactive
 * proxy and avoid relying on the original object.
 *
 * A reactive object also automatically unwraps refs contained in it, so you
 * don't need to use `.value` when accessing and mutating their value:
 *
 * ```js
 * const count = ref(0)
 * const obj = reactive({
 *   count
 * })
 *
 * obj.count++
 * obj.count // -> 1
 * count.value // -> 1
 * 
 */

// 对不同的响应式对象,分别在内部声明了不同的weakMap用来储存。
export const reactiveMap = new WeakMap<Target, any>()
export const shallowReactiveMap = new WeakMap<Target, any>()
export const readonlyMap = new WeakMap<Target, any>()
export const shallowReadonlyMap = new WeakMap<Target, any>()


// 常规reactive()函数定义如下,返回的是非shallow非readonly的响应式对象。
// 类似地,还有shallowReactive()、readonly()、shallowReadonly(),用来创建不同类型的响应式对象。
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
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,
    // 传入对应的handler,对于普通reactive对象就是mutableHandler,这些handler决定了对target执行各种操作时发生的行为。
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  )
}

// 这里传入的target是Target类型:
// 它的定义:也可以是原始object,但可选具有以下四个内部属性:__v_skip,__v_isReactive,__v_isReadonly,__v_raw。
export const enum ReactiveFlags {
  // SKIP为ture,代表这个对象永远不会成为reactive对象
  SKIP = '__v_skip',

  // IS_REACTIVE为true,代表为reactive对象
  IS_REACTIVE = '__v_isReactive',

  // IS_READONLY为true,代表这是只读reactive对象
  IS_READONLY = '__v_isReadonly',

  // RAW中记录了reactive proxy对象代理的原始值
  RAW = '__v_raw'
}
export interface Target {
  [ReactiveFlags.SKIP]?: boolean
  [ReactiveFlags.IS_REACTIVE]?: boolean
  [ReactiveFlags.IS_READONLY]?: boolean
  [ReactiveFlags.RAW]?: any
}

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
// 非对象的原始值不能用reactive()转化
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // 如果target本身就是一个reactive proxy,则直接返回。
  // exception: calling readonly() on a reactive object
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }

  // 已经注册过的target proxy,直接获取并返回。
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }

  // 根据不同的target类型,创建设置不同的proxy handler,返回对应proxy。
  // 这些handler内部,定义了对target各种操作的拦截器,在各个调用时机执行trace或trigger方法,用来实现target的响应式。
  // target就是传入用来创建响应式的原始变量。
  //
  // Vue定义的target类型有三种:COMMON/COLLECTION/INVALID
  // COMMON: object和array
  // COLLECTION: map,set,weakmap,weakset
  // 其他都是INVALID:直接返回原target。
  // 
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  
  // 这里使用了不同的handler,创建proxy,然后返回。
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  // 在对应的map记录新生成的proxy。 然后返回proxy
  proxyMap.set(target, proxy)
  return proxy
}

2. 用于创建响应式对象proxy的拦截器:baseHandlers和collectionHandlers

// 普通reactive()函数的proxy handler
// 只捕获五种操作:Get,set,deleteProperty,has和ownkeys
export const mutableHandlers: ProxyHandler<object> = {
   get,
   set,
   deleteProperty,
   has,
   ownKeys
}

// 下面拿一个最常见的get捕获器,进行说明。

// 普通reactive()函数proxy get拦截器的定义。
const get = createGetter()
function createGetter(isReadonly = false, shallow = false) {
  // 这里设置了读取(GET)一个Target的某一个key时所进行的操作。
  return function get(target: Target, key: string | symbol, receiver: object) {
    // get的target属性是内部属性:ReactiveFlags.IS_REACTIVE、ReactiveFlags.IS_READONLY、ReactiveFlags.RAW时的返回值。
    // ReactiveFlags.RAW返回原始对象target, 其他按情况返回对应布尔值。
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (
  // Marswiz: receiver是捕获器中,传入的调用这个捕获器的原始对象。
  // 这里的含义是:只有从各个Map中注册过的proxy get这个raw值(也就是通过createReactive、createReadonly这类工厂函数创建的proxy).
  // 才返回原始target,其他proxy对象即使有ReactiveFlags.RAW属性,也用这个get捕获器,也不会返回target,因为vue不认这个对象,它不是从Vue内部创建的。
      key === ReactiveFlags.RAW &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
            ? shallowReactiveMap
            : reactiveMap
        ).get(target)
    ) {
      return target
    }

    // !!注意:如果target是数组对象,有特殊规则限制!
    const targetIsArray = isArray(target)
    // 对于数组的target,如果get的key是arrayInstrumentations中注册的,则执行arrayInstrumentations内对应的方法。
    // arrayInstrumentations主要拦截数组的三种操作: 'includes', 'indexOf', 'lastIndexOf'
    // 这三种操作因为对数组元素的身份敏感,如果一个数组内还有嵌套的reactive元素,那么在执行这些操作的时候有可能出现错误结果。
    // arrayInstrumentations 保证了无论是直接按照内部reactive()对象执行这些方法还是通过原始值RawValue,都能获取到正确的结果。
    // 同时,在执行这些方法的时候,如果数组target本身没有被track,则按数组的索引字符串为key进行track。
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }

    // 获取Target对应key的目标值res。
    const res = Reflect.get(target, key, receiver)
    //  内置symbol作为key,或者没有被track的key,都直接返回目标值。
    if (
      isSymbol(key)
        ? builtInSymbols.has(key as symbol)
        : isNonTrackableKeys(key)
    ) {
      return res
    }

    //  ★★★★★★★    关键操作    ★★★★★★★
    //  非只读target,对target.key进行track。 
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }
    //  浅响应式对象,直接返回目标结果。
    if (shallow) {
      return res
    }

    //  如果目标值本身直接是ref对象,直接返回Ref.value
    //  !!!注意这里有一个例外:target是Array数组类型,且key是整数索引时,默认直接返回ref而不是ref.value;
    //  也就是说,在响应式数组中,保存的ref,在执行get操作的时候,返回值仍然是ref,需要进行.value操作才能获取到结果。
    if (isRef(res)) {
      // ref unwrapping - does not apply for Array + integer key.
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res
    }

    //  如果目标值是对象类型,按是否只读,转化为 只读响应式对象 or 普通响应式对象 返回。
    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)
    }

    // 其他默认情况,都直接返回res目标值。
    return res
  }
}

readonly proxy的拦截器:

// Marswiz: 只读对象的proxy捕获器
// 只拦截get/set/deleteProperty三个操作。
export const readonlyHandlers: ProxyHandler<object> = {
  get: readonlyGet,
  // 可以看到,这里Set和deleteProperty都不进行任何操作,且开发环境会报错。
  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
  }
}

3. ref()函数

// ref的几个重载:
export function ref<T extends object>(value: T): ToRef<T>
export function ref<T>(value: T): Ref<UnwrapRef<T>>
export function ref<T = any>(): Ref<T | undefined>
export function ref(value?: unknown) {
  return createRef(value)
}

// createRef(): 可以看到这里是返回了一个RefImpl类的实例。
function createRef(rawValue: unknown, shallow = false) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

// RefImpl Class类
class RefImpl<T> {
  private _value: T

  public readonly __v_isRef = true

  constructor(private _rawValue: T, public readonly _shallow = false) {
// 根据是否shallow(浅响应式),来确定内部_value的值:浅的话就直接传入原始值,非浅就把原始值深度转化为响应式再传入。
    this._value = _shallow ? _rawValue : convert(_rawValue)
  }

// 这里是ref响应式的核心内容:
// ① 在get这个ref.value的时候,对这个响应式对象的原始对象value属性进行GET形式的Track;
// ② 在set这个ref.value的时候,对新的值newVal和旧的值value进行比较,如果有改动,就将内部的_rawValue修改为newVal,然后将_value修改为响应式的newVal,然后用newVal对原始对象的value属性进行SET trigger。
// 
// ★★★
// toRaw的函数定义:这里使用了逻辑表达式进行递归,很难理解。
// 结果就是通过传入对象observed,层层递归找到最内层的[ReactiveFlags.RAW]值返回。如果对象observed本身就不带有[ReactiveFlags.RAW]值,说明本身就是原始对象,就返回对象本身。
// export function toRaw<T>(observed: T): T {
//     return (
//         (observed && toRaw((observed as Target)[ReactiveFlags.RAW])) || observed
// )
// }
  get value() {
    track(toRaw(this), TrackOpTypes.GET, 'value')
    return this._value
  }

  set value(newVal) {
    if (hasChanged(toRaw(newVal), this._rawValue)) {
      this._rawValue = newVal
      this._value = this._shallow ? newVal : convert(newVal)
      trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
    }
  }
}
Keywords: Vue
previousPost nextPost
已经有 1000000 个小伙伴看完了这篇推文。