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