More articles

preface

I have been using the combined API for a while, so I should read the code of Yuda with an attitude of knowing what it is and why. Please share the part about Reactive and analyze it one by one according to the APIreactive part on the official website. You can refer to the simplified code for easy understanding

Simplify the code

Vue3-reactive source location

Proxy

Vue3 uses Proxy to replace defineProperty to solve the drawbacks brought by defineProperty, and reactive is closely related to Proxy. For those who do not know Proxy, please see Ruan Yinfong ES6 introduction, and then start source analysis

reactive

See how reactive is defined in the source code

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,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  )
}
// createReactiveObject
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  if(! isObject(target)) {if (__DEV__) {
      console.warn(`value cannot be made reactive: The ${String(target)}`)}return target
  }
  // target is already a Proxy, return it.
  // exception: calling readonly() on a reactive object
  if( target[ReactiveFlags.RAW] && ! (isReadonly && target[ReactiveFlags.IS_REACTIVE]) ) {return target
  }
  // target already has corresponding Proxy
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only a whitelist of value types can be observed.
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}
Copy the code

The createReactiveObject method is used to create a reactive method. This code and createReactiveObject are simplified as follows:

function reactive(target) {
  return createReactiveObject(target, reactiveMap, mutableHandlers)
}
function createReactiveObject(target, proxyMap, baseHandlers) {
  // Non-object return
  if(! isObject(target)) {return target
  }
  // Whether it already exists
  const existingProxy = proxyMap.get(target)
  // There is a return
  if(existingProxy) return existingProxy
  // Determine the type
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) return target
  // Use proxy to generate a new object
  const proxy = new Proxy(target, baseHandlers)
  // Cache data
  proxyMap.set(target, proxy)

  return proxy
}
Copy the code

You can see that a Proxy instance is returned, and reactive is implemented as follows:

function reactive(target) {
  return new Proxy(target, {
    get,
    set
  })
}
Copy the code

The implementation of get, set and so on is very important, the implementation of the source code is still more complex, here to simplify (try to use the code in the source code) :

/ * * *@param IsReadOnly Whether to read only *@param Shallow is shallowReadonly or shallowReactive */
function createGetter(isReadOnly = false, shallow = false) {
  return function get(target, key, receiver) {
    // Check whether it is reactive
    if(key === IS_REACTIVE) return! isReadOnly// Check whether it is readonly
    else if(key === IS_READONLY) return isReadOnly
    // toRaw operation judgment
    else if(
      key === RAW &&
      receiver === 
        (
          isReadOnly
            ? shallow
              ? shallowReadonlyMap
              : readonlyMap
            : shallow
              ? shallowReactiveMap
              : reactiveMap
        ).get(target)
    ) return target

    const result = Reflect.get(target, key, receiver)
    // Shallow is true, if true, no further processing is performed
    if (shallow) return result
    // If it is an object, it is processed in depth
    if(isObject(result)) return isReadOnly ? readonly(result) : reactive(result)
    
    return result
  }
}

function createSetter() {
  return function set(target, key, value, receiver) {
    console.log(`${key}Has changed)
    const result = Reflect.set(target, key, value, receiver)
    return result
  }
}

const get = createGetter();
const set = createSetter();
Copy the code

Here you can see that rective does some deep processing on the object, simplifying get as follows:

function createGetter(isReadOnly = false, shallow = false) {
  return function get(target, key, receiver) {
    const result = Reflect.get(target, key, receiver)
    // If it is an object, it is processed in depth
    if(isObject(result)) return isReadOnly ? readonly(result) : reactive(result)
    
    return result
  }
}
Copy the code

readonly

When the readonly method is called, isReadOnly in GET is set to true, and methods such as set and deleteProperty are overridden to prevent and prompt read-only

const readonlyHandlers = {
  get: createGetter(true),
  set: (target, key) = > {
    console.warn(
      `Set operation on key "The ${String(key)}" failed: target is readonly.`,
      target
    );
    return true
  },
  deleteProperty(target, key) {
    if (__DEV__) {
      console.warn(
        `Delete operation on key "The ${String(key)}" failed: target is readonly.`,
        target
      )
    }
    return true}}function readonly(target) {
  return createReactiveObject(target, readonlyMap, readonlyHandlers)
}
Copy the code

shallowReactive

When the shallowReactive method is called, shallow in GET is set to true and the data will not be processed further

function shallowReactive(target) {
  return createReactiveObject(target, shallowReactiveMap, shallowReactiveHandlers)
}
const shallowReactiveHandlers = {
  get: createGetter(false.true),
  set
}
Copy the code

shallowReadonly

Set both isReadOnly and shallow to true

function shallowReadonly(target) {
  return createReactiveObject(target, shallowReadonlyMap, shallowReadonlyHandlers)
}
const shallowReadonlyHandlers = {
  get: createGetter(true.true),
  set(target, key) {
    // Readonly responsive objects cannot be modified
    console.warn(
      `Set operation on key "The ${String(key)}" failed: target is readonly.`,
      target
    );
    return true; }};Copy the code

isReactive

When the IS_REACTIVE attribute is fetched, the get method returns! IsReadOnly: Reactive if not read-only

function isReactive(value) {
  return!!!!! value[IS_REACTIVE] }Copy the code

isReadonly

When the IS_READONLY property is retrieved, isReadOnly is returned in the get method

function isReadonly(value) {
  return!!!!! value[IS_READONLY] }Copy the code

isProxy

IsProxy is isReactive or isReadonly

function isProxy(value) {
  return isReactive(value) || isReadonly(value)
}
Copy the code

toRaw

When we call the toRaw method, we will get value[RAW]. The get method will judge this attribute and get the corresponding cache data. If it hits, the RAW data will be returned (corresponding to the three-entry operation in get).

function toRaw(value) {
  const raw = value[RAW]
  return raw ? toRaw(raw) : value
}
Copy the code

markRaw

MarkRaw adds an identifier skip to data, and createReactiveObject immediately returns the original object when skip is judged to have hit, never going to the proxy

function markRaw(value) {
  def(value, SKIP, true)
  return value
}
Copy the code

conclusion

The code looks better with simplicity