In the previous article Vue3 source code analysis — REF, I read and analyzed Vue3 responsive REF related source code implementation in detail. In this article, I will lead you to learn the implementation of Vue3 responsive API Reactive source code.

Compared with vue2, Vue3 uses Proxy to replace the data Proxy interception operation of Object.defineProperty used in VUe2, which greatly improves the performance of reactive data Proxy processing.

Reactive API (reactive API)

1.reactive

Official description: Returns a reactive copy of the object. The reactive conversion is “deep” — it affects all nested properties, and the returned proxy is not equal to the original object. You are advised to use only reactive proxies to avoid relying on raw objects.

1.1 Source Code Definition
  • File definition: Packages \reactivity\ SRC \ react.ts

Before we look at the source code implementation of Reactive, we will first look at the types and interfaces of Reactive defined at the beginning of the document. Understanding these types will help us read the source code analysis of Reactive

// Reactive Related identifier
export const enum ReactiveFlags {
  SKIP = '__v_skip'.// Skip the proxy
  IS_REACTIVE = '__v_isReactive'.// Reactive Proxy object id
  IS_READONLY = '__v_isReadonly'.// Read-only proxy id
  RAW = '__v_raw' // The original value identifier of the proxy object
}
// Proxy object interface description
exportinterface Target { [ReactiveFlags.SKIP]? : boolean [ReactiveFlags.IS_REACTIVE]? : boolean [ReactiveFlags.IS_READONLY]? : boolean [ReactiveFlags.RAW]? : any }// Proxy object type
const enum TargetType {
  INVALID = 0./ / null and void
  COMMON = 1.// The object and array types that can be proxies
  COLLECTION = 2 WeakMap,weakSet,weakSet
}
// Four classes of Reactive related Maps are defined here to cache proxy objects
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>()
// Tool function, detect the type of proxy Object, and distinguish Object,Array and Map,Set,WeakMap,WeakSet types
function targetTypeMap(rawType: string) {
  switch (rawType) {
    case 'Object':
    case 'Array':
      return TargetType.COMMON / / 1
    case 'Map':
    case 'Set':
    case 'WeakMap':
    case 'WeakSet':
      return TargetType.COLLECTION / / 2
    default:
      return TargetType.INVALID / / 0}}// Utility functions - get the type of the proxy object
function getTargetType(value: Target) {
  return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
    ? TargetType.INVALID
    : targetTypeMap(toRawType(value))
}
Copy the code
1.2 source code implementation
export function reactive(target: object) {
  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
    return target
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  )
}
Copy the code

Description:

  • First check whether target is already a read-only proxy object. If it is, return target itself
  • Call the createReactiveObject() method for the proxy operation

2. shallowReactive

Official description: Creates a reactive proxy that tracks the responsiveness of its own property, but does not perform deep reactive transformations of nested objects (exposing raw values).

  • ShallowReactive refers to the shallow proxy, which only makes the property value of the object responsive. If the value of the property of the object is complex, it will not be recursively processed to make the complex type value also responsive, that is, only one layer is processed.
2.1 Source code implementation
export function shallowReactive<T extends object> (target: T) :T {
  return createReactiveObject(
    target,
    false,
    shallowReactiveHandlers,
    shallowCollectionHandlers,
    shallowReactiveMap
  )
}
Copy the code

Internal direct call createReactiveObject() method return, about createReactiveObject() method, we put in the following detailed in-depth analysis

3. readonly

Official description: A read-only proxy that accepts an object (reactive or pure) or ref and returns the original object. A read-only proxy is deep: any nested property accessed is also read-only.

3. 1 source code implementation
export function readonly<T extends object> (
  target: T
) :DeepReadonly<UnwrapNestedRefs<T>> {
  return createReactiveObject(
    target,
    true,
    readonlyHandlers,
    readonlyCollectionHandlers,
    readonlyMap
  )
}
Copy the code

4. shallowReadonly

Official description: Create a proxy that makes its own property read-only, but does not perform deep read-only conversion of nested objects (exposing raw values)

4.1 Source code implementation
export function shallowReadonly<T extends object> (
  target: T
) :Readonly<{ [K in keyof T]: UnwrapNestedRefs<T[K]> }> {
  return createReactiveObject(
    target,
    true,
    shallowReadonlyHandlers,
    shallowReadonlyCollectionHandlers,
    shallowReadonlyMap
  )
}
Copy the code

5. isReadonly

Official description: Checks whether the object is a read-only proxy created by ReadOnly.

5.1 Source code implementation
export function isReadonly(value: unknown) :boolean {
  return!!!!! (value && (valueas Target)[ReactiveFlags.IS_READONLY])
}
Copy the code

Note: Infer the incoming parameter value type to the previously declared Target type and check for the presence of the __v_isReadonly attribute identifier

6. isReactive

Official description: Checks whether an object is a reactive agent created by Reactive

6.1 Source code Implementation
export function isReactive(value: unknown) :boolean {
  if (isReadonly(value)) {
    return isReactive((value as Target)[ReactiveFlags.RAW])
  }
  return!!!!! (value && (valueas Target)[ReactiveFlags.IS_REACTIVE])
}
Copy the code

Description:

  • Check whether it is created by the Readonly API. If it is created by the Readonly API, check whether the readonly source object is reactive
  • Otherwise, the default returns whether the passed argument contains the __v_isReactive flag

7. isProxy

Official description: Check whether the object is a proxy created by Reactive or Readonly

7.1 Source Code Definition
export function isProxy(value: unknown) :boolean {
  return isReactive(value) || isReadonly(value)
}
Copy the code

IsProxy source code is very simple, that is, call isReactive and isReadonly methods

8. toRaw

Official description: Returns the original object of the Reactive or Readonly agent

8.1 Source Code Definition
export function toRaw<T> (observed: T) :T {
  return (
    (observed && toRaw((observed as Target)[ReactiveFlags.RAW])) || observed
  )
}
Copy the code

Description:

  • Returns the __v_raw attribute value of the passed argument or returns the original value

9. markRaw

Official description: Marks an object so that it will never be converted to proxy.

9.1 Source Code Definition
export function markRaw<T extends object> (value: T) :T {
  def(value, ReactiveFlags.SKIP, true)
  return value
}

// def is a utility method for the source public module
export const def = (obj: object, key: string | symbol, value: any) = > {
  Object.defineProperty(obj, key, {
    configurable: true.enumerable: false,
    value
  })
}
Copy the code

Description:

  • MarkRaw internally calls the def method, passing in the accepted arguments value, __v_skip, and true3, and returning the values of the passed arguments
  • The def method modifies the configuration properties and enumerable properties of the incoming Object through Object.defineProperty

10.createReactiveObject

Reactive, shallowReactive, readonly, and shallowReadonly are all implemented by calling createReactiveObject (createReactiveObject)

10.1 Source Code Definition
function createReactiveObject(
  target: Target,
  isReadonly: boolean, // reactive false
  baseHandlers: ProxyHandler<any>, // reactive mutableHandlers
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any> // reactive reactiveMap
) {
  if(! isObject(target)) {/ / if not Object, that is, not Object, Array, Map, Set, WeakMap, WeakSet type, can not agent, returned directly
    if (__DEV__) {
      // Give a warning in the development environment
      console.warn(`value cannot be made reactive: The ${String(target)}`)}return target
  }
  // If target is already a proxy object, return it directly
  if( target[ReactiveFlags.RAW] && ! (isReadonly && target[ReactiveFlags.IS_REACTIVE]) ) {return target
  }
  // If target already has a proxy object, find the proxy object and return it
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // Get the type of target. If it is INVALID, the proxy cannot be performed, so return target
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  // Generate the corresponding proxy object based on target
  const proxy = new Proxy(
    target,
    // Differentiate object array from map,set,weakMap,weakSet
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers 
  )
  // Cache the proxy object in the corresponding proxyMap
  proxyMap.set(target, proxy)
  return proxy
}
Copy the code

Description:

  • Reactive, shallowReactive, readonly, shallowReadonly in these four API calls createReactiveObject method, were introduced into their corresponding new Proxy () constructor get and set operation function, Execute their internal implementations separately
  • CreateReactiveObject proxyMap is Reactive, shallowReactive, ReadOnly, and shallowReadonly. Weakmaps are created to cache their respective proxy objects.

11. reactive Proxy get handler–mutableHandlers

We then take a closer look at Proxy Get Handlers, one of the core implementations of the Reactive API

const get = /*#__PURE__*/ createGetter()
const set = /*#__PURE__*/ createSetter()
function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    / / access __v_isReactive
    if (key === ReactiveFlags.IS_REACTIVE) {
      return! isReadonly }else if (key === ReactiveFlags.IS_READONLY) {
      / / access __v_isReadonly
      return isReadonly
    } else if (
      // If the key is __v_raw, return target directly
      key === ReactiveFlags.RAW &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
            ? shallowReactiveMap
            : reactiveMap
        ).get(target)
    ) {
      return target
    }

    const targetIsArray = isArray(target)
    // If target is an array and key belongs to one of the eight methods ['includes', 'indexOf', 'lastIndexOf','push', 'pop', 'shift', 'unshift', 'splice']
    if(! isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {return Reflect.get(arrayInstrumentations, key, receiver)
    }
    const res = Reflect.get(target, key, receiver)
    // If the key is symbol and belongs to one of Symbol's built-in methods, or if the object is a prototype, the result is returned without collecting dependencies.
    if (
      isSymbol(key)
        ? builtInSymbols.has(key as symbol)
        : isNonTrackableKeys(key)
    ) {
      return res
    }
    // Collect dependencies if they are not read-only objects
    if(! isReadonly) { track(target, TrackOpTypes.GET, key) }// Shallow responses are returned immediately without recursive calls to reactive()
    if (shallow) {
      return res
    }
    // If it is a ref object, the real value is returned, i.e. Ref. Value, except for arrays.
    if (isRef(res)) {
      constshouldUnwrap = ! targetIsArray || ! isIntegerKey(key)return shouldUnwrap ? res.value : res
    }
    // If the target[key] value is an object, it will continue to be proxied
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}

function createSetter(shallow = false) {
  return function set(target: object, key: string | symbol, value: unknown, receiver: object) :boolean {
    / / to take the old value
    let oldValue = (target as any)[key]
    // In the depth proxy case, we need to manually handle the case where the attribute value is ref and give trigger to ref
    if(! shallow) { value = toRaw(value) oldValue = toRaw(oldValue)if(! isArray(target) && isRef(oldValue) && ! isRef(value)) { oldValue.value = valuereturn true}}else {
      // in shallow mode, objects are set as-is regardless of reactive or not
    }
    // Determine whether to add or modify
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    const result = Reflect.set(target, key, value, receiver)
    if (target === toRaw(receiver)) {
      if(! hadKey) {// If the target does not have a key, it is a new operation that needs to trigger a dependency
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        // Update -- dependencies are triggered if the old and new values are not equal
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}
export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}
Copy the code