The project address

Vue 3.0, version pre-alpha

Data response source address

packages->reactivity->src->reactive.ts

Add comments to incoming code

isObject

export constisObject = (val: any): val is Record<any, any> => val ! = =null && typeof val === 'object'
// Introduce isObject functions in packages->shared folder
// Type protection, al is Record type predicate, whenever the isObject function is called, any value passed must be a parameter name in the Record function. This function defines any generic type, returns a Boolean value to determine whether it is an object, and cannot be null
Copy the code

toTypeString

export const objectToString = Object.prototype.toString
export const toTypeString = (value: unknown): string= > objectToString.call(value)
// Introduce toTypeString functions in packages->shared folder
ToTypeString / / type judgment, call this function when introduced to a safe any type (unknown), and must be a string, and then through the Object. The prototype. The toString, call () the way to judge whether a string
// The main difference between unknown and any is that the unknown type is more rigorous: before most operations can be performed on values of unknown type, we must perform some form of checking. We don't have to do any checking before we operate on a value of type any.
// (value: unknown): string may indicate that the parameter must be of unknown type and the return value is of string type
Copy the code

builtInSymbols

const builtInSymbols = new Set(
  Object.getOwnPropertyNames(Symbol)
    .map(key= > (Symbol as any)[key])
    .filter(value= > typeof value === 'symbol'))/ / incoming packages/reactivity/SRC/baseHandlers builtInSymbols in ts
// Define a Set collection class
/ / Object. GetOwnPropertyNames (Symbol) return to Symbol all attributes of an enumerable and an enumeration name string
// map(key => (Symbol as any)[key]) convert js map(function (key) {return Symbol[key]; }), as any, to prevent the TS compiler from complaining that the key and Symbol are not related by type, and that the TS compiler is afraid of accessing properties that do not exist
// filter(value => typeof value === 'symbol') filter value type is symbol data
Copy the code

JS includes special symbols (such as iterators) that represent attributes of an object, but this attribute is not dependent on the collection response, so it should be excluded. The main purpose of this code is to determine which properties (Symbol) match this condition.

isRef

export const refSymbol = Symbol(__DEV__ ? 'refSymbol' : undefined)
export function isRef(v: any) :v is Ref<any> {
  return v ? v[refSymbol] === true : false
}
/ / incoming packages/reactivity/SRC/ref. IsRef in ts
// Type protection, the attribute of the incoming data must be the Symbol of the refSymbol to be true, otherwise false
Copy the code

track

export const enum OperationTypes {
  // Replace numbers with literal strings to make it easier to check
  // debugger events
  SET = 'set',
  ADD = 'add',
  DELETE = 'delete',
  CLEAR = 'clear',
  GET = 'get',
  HAS = 'has',
  ITERATE = 'iterate'
}
WeakMap Weakly refers to a map object, which is mainly used to avoid memory leaks
const targetMap = new WeakMap<any, KeyToDepMap>()

/ / / / / / / / / / / / / / / / / / / the above quoted from other documents / / / / / / / / / / / / / / / / / / / / /

export interface ReactiveEffect {
  (): any
  isEffect: true
  active: boolean
  raw: Function
  deps: Array<Dep> computed? : boolean scheduler? :(run: Function) = > voidonTrack? :(event: DebuggerEvent) = > voidonTrigger? :(event: DebuggerEvent) = > voidonStop? :(a)= > void
}
/ / regulations activeReactiveEffectStack ReactiveEffect interfaces must comply with the above
export const activeReactiveEffectStack: ReactiveEffect[] = []

export function track(target: any, type: OperationTypes, key? : string | symbol) {
  // Do not execute if it is not traced
  if(! shouldTrack) {return
  }
  // Get traced attributes
  const effect = activeReactiveEffectStack[activeReactiveEffectStack.length - 1]
  // Perform the operation if there is a value
  if (effect) {
    // If the type is an iterator, the key is set to the iterator's Symbol
    if (type === OperationTypes.ITERATE) {
      key = ITERATE_KEY
    }
    // Get the stored value
    let depsMap = targetMap.get(target)
    // If I am not, I will store it. Void 0 is undpay, so THAT I therefore therefore pay
    if (depsMap === void 0) {
      targetMap.set(target, (depsMap = new Map()))}// Get the key. The key value passed in cannot be empty
    let dep = depsMap.get(key!)
    // If not, store it
    if (dep === void 0) { depsMap.set(key! , (dep =new Set()))}// If effect is not found in the store, add it, and add dep to effect's DEps property
    if(! dep.has(effect)) { dep.add(effect) effect.deps.push(dep)if (__DEV__ && effect.onTrack) {
        effect.onTrack({
          effect,
          target,
          type,
          key
        })
      }
    }
  }
}
Copy the code

The main purpose of this function is to go deep into the tracing object and store the traced data

createGetter

function createGetter(isReadonly: boolean) {
  return function get(target: any, key: string | symbol, receiver: any) {
    const res = Reflect.get(target, key, receiver)
    if (typeof key === 'symbol' && builtInSymbols.has(key)) {
      return res
    }
    if (isRef(res)) {
      return res.value
    }
    // Use the track function to do depth tracking
    track(target, OperationTypes.GET, key)
    return isObject(res)
      ? isReadonly
        ? // need to lazy access readonly and reactive here to avoid
          // circular dependency
          // Loop dependencies need to be avoided with read-only and delayed responses
          readonly(res)
        : reactive(res)
      : res
  }
}
/ / incoming packages/reactivity/SRC/baseHandlers createGetter function of ts
// Create a function that passes in a Boolean value and intercepts a read of an attribute of the data
// target Target object
// Name of the key attribute
// The name of the receiver property and the proxy instance itself (strictly speaking, the object against which the action is directed)
// Reflect es6 native API, Reflect object method and Proxy object method one-to-one correspondence, as long as the Proxy object method, can be found on the corresponding method
// In the above code, the corresponding Reflect method is called internally for each Proxy object's get operation to ensure that the native behavior executes properly
Copy the code

This function intercepts the get operation on the data and further processes the underlying data returned by Reflect to determine if it is an object. If so, it loops again

mutableHandlers

export const mutableHandlers: ProxyHandler<any> = {
  get: createGetter(false),
  set,
  deleteProperty,
  has,
  ownKeys
}
/ / incoming packages/reactivity/SRC/baseHandlers mutableHandlers in ts
// Type assertion, define some proxy handler, mark a 
      
        generic, the compiler here will provide ProxyHandler code properties hint
      
Copy the code

If the data is mutable, do this

readonlyHandlers

export const readonlyHandlers: ProxyHandler<any> = {
  get: createGetter(true),

  set(target: any, key: string | symbol, value: any, receiver: any): boolean {
    if (LOCKED) {
      if (__DEV__) {
        console.warn(
          `Set operation on key "The ${String(key)}" failed: target is readonly.`,
          target
        )
      }
      return true
    } else {
      return set(target, key, value, receiver)
    }
  },

  deleteProperty(target: any, key: string | symbol): boolean {
    if (LOCKED) {
      if (__DEV__) {
        console.warn(
          `Delete operation on key "The ${String(
            key
          )}" failed: target is readonly.`,
          target
        )
      }
      return true
    } else {
      return deleteProperty(target, key)
    }
  },

  has,
  ownKeys
}
Copy the code

Error: Failed: target is readonly: Failed: target is readonly: Failed: target is readonly

hasOwn

const hasOwnProperty = Object.prototype.hasOwnProperty
export const hasOwn = (
  val: object,
  key: string | symbol
): key is keyof typeof val => hasOwnProperty.call(val, key)
The hasOwnProperty() method returns a Boolean indicating whether the object has the specified property in its own properties (that is, whether it has the specified key).
/ / is there is a special keyword in ts, which can be used to determine a variable belongs to | an interface type
// Set is called twice when the array push is called, such as arr.push(1).
// For the first set, add 1 to the end of the array
// Set the second time to add the length attribute to the array
Copy the code

createInstrumentationGetter

function createInstrumentationGetter(instrumentations: any) {
  return function getInstrumented(target: any, key: string | symbol, receiver: any) {
    "// Verify that the key is an instrumentations attribute, and the target contains the key; otherwise, the target is used
    // Key in target in
    // When "target" is an array, "key" refers to the array "index"; When "target" is an object, "key" refers to the "property" of the object.
    target =
      hasOwn(instrumentations, key) && key in target ? instrumentations : target
    return Reflect.get(target, key, receiver)
  }
}
Copy the code

The getInstrumented function returns when called for the first time. The getInstrumented function is used to avoid multiple proxy operations

mutableCollectionHandlers

export const mutableCollectionHandlers: ProxyHandler<any> = {
  get: createInstrumentationGetter(mutableInstrumentations)
}
// Variable data handling functions
Copy the code

readonlyCollectionHandlers

export const readonlyCollectionHandlers: ProxyHandler<any> = {
  get: createInstrumentationGetter(readonlyInstrumentations)
}
// Read-only data processing functions
Copy the code

UnwrapNestedRefs

export interface Ref<T> {
  [refSymbol]: true
  value: UnwrapNestedRefs<T>
}
export type UnwrapNestedRefs<T> = T extends Ref<any> ? T : UnwrapRef<T>
// T extends Ref
      
        if T can be assigned to Ref then T, otherwise UnwrapRef
       
      
Copy the code

The function is to correctly (recursively) deduce the type of business data when a Ref nested Ref is encountered

UnwrapRef

// Recursively unwraps nested value bindings.
// Recursively open nested binding values
export type UnwrapRef<T> = {
  ref: T extends Ref<infer V> ? UnwrapRef<V> : T
  array: T extends Array<infer V> ? Array<UnwrapRef<V>> : T
  object: { [K in keyof T]: UnwrapRef<T[K]> }
  stop: T
}[T extends Ref<any>
  ? 'ref'
  : T extends Array<any>
    ? 'array'
    : T extends BailTypes
      ? 'stop' // Exit on types that shouldn't be unwrapped // Exit on types that shouldn't be unwrapped
      : T extends object ? 'object' : 'stop']
// T extends Ref
      
        Infer prefix indicates the type to be returned. T extends Ref
       
         indicates the return type that T can assign to Ref
       
      
Copy the code

ReactiveEffect

export interface ReactiveEffect {
  (): any
  isEffect: true
  active: boolean
  raw: Function
  deps: Array<Dep> computed? : boolean scheduler? :(run: Function) = > voidonTrack? :(event: DebuggerEvent) = > voidonTrigger? :(event: DebuggerEvent) = > voidonStop? :(a)= > void
}
// Define the interface
Copy the code

Annotate source code

import { isObject, toTypeString } from '@vue/shared'
import { mutableHandlers, readonlyHandlers } from './baseHandlers'

import {
  mutableCollectionHandlers,
  readonlyCollectionHandlers
} from './collectionHandlers'

import { UnwrapNestedRefs } from './ref'
import { ReactiveEffect } from './effect'

// The main WeakMap that stores {target -> key -> dep} connections.
// Conceptually, it's easier to think of a dependency as a Dep class
// which maintains a Set of subscribers, but we simply store them as
// raw Sets to reduce memory overhead.
// Conceptually, it is easier to think of dependencies as maintaining a Set of subscriber DEP classes, but we simply store them as raw sets to reduce memory overhead.
export type Dep = Set<ReactiveEffect>
export type KeyToDepMap = Map<string | symbol, Dep>
export const targetMap = new WeakMap<any, KeyToDepMap>()

// WeakMaps that store {raw <-> observed} pairs.
WeakMap stores the mapping of {RAW <-> Observed}
WeakMap Weakly refers to a map object, which is mainly used to avoid memory leaks
const rawToReactive = new WeakMap<any, any>()
// Storage is mutable
const reactiveToRaw = new WeakMap<any, any>()
const rawToReadonly = new WeakMap<any, any>()
// Store read-only
const readonlyToRaw = new WeakMap<any, any>()

// WeakSets for values that are marked readonly or non-reactive during
// observable creation.
// WeakSet is used to create a mark for read-only or non-responsive objects
// WeakSet weak reference Set, cannot store value, can only store object reference, mainly to avoid memory leakage
const readonlyValues = new WeakSet<any>()
const nonReactiveValues = new WeakSet<any>()

// A collection of all storage types
const collectionTypes = new Set<Function> ([Set.Map.WeakMap.WeakSet])
// Set whitelist regex
const observableValueRE = /^\[object (?:Object|Array|Map|Set|WeakMap|WeakSet)\]$/

// Start checking which values can be proxy
const canObserve = (value: any): boolean= > {
  return (
    // Not a vUE object! value._isVue &&/ / not a vNode! value._isVNode &&/ / white list: Object | Array Map for | | Set | WeakMap | WeakSet
    observableValueRE.test(toTypeString(value)) &&
    // There is no proxy! nonReactiveValues.has(value) ) }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 the proxy is read-only, the read-only version is returned
  if (readonlyToRaw.has(target)) {
    return target
  }
  // target is explicitly marked as readonly by user
  // If target is set to read-only by the user, make it read-only and return
  if (readonlyValues.has(target)) {
    return readonly(target)
  }
  return createReactiveObject(
    target,
    // Below are a bunch of WeakMaps set by the above code
    rawToReactive,
    reactiveToRaw,
    mutableHandlers,
    mutableCollectionHandlers
  )
}

export function readonly<T extends object> (
  target: T
) :Readonly<UnwrapNestedRefs<T>>
export function readonly(target: object) {
  // value is a mutable observable, retrieve its original and return
  // a readonly version.
  // If the value detected is mutable, find the original value and return the read-only version
  if (reactiveToRaw.has(target)) {
    target = reactiveToRaw.get(target)
  }
  return createReactiveObject(
    target,
    rawToReadonly,
    readonlyToRaw,
    readonlyHandlers,
    readonlyCollectionHandlers
  )
}

// Create a responsive object
function createReactiveObject(target: any, toProxy: WeakMap
       
        , toRaw: WeakMap
        
         , baseHandlers: ProxyHandler
         
          , collectionHandlers: ProxyHandler
          
         
        ,>
       ,>) {
  // Determine if the target type is an object, and send a warning if not
  if(! isObject(target)) {if (__DEV__) {
      console.warn(`value cannot be made reactive: The ${String(target)}`)}return target
  }
  // target already has corresponding Proxy
  // If the Proxy has already been processed, do not need to be processed again
  let observed = toProxy.get(target)
  // If the value is not empty, void 0 is undpay, so I therefore pay therefore I am
  if(observed ! = =void 0) {
    return observed
  }
  // target is already a Proxy
  // Target is already a Proxy
  if (toRaw.has(target)) {
    return target
  }
  // only a whitelist of value types can be observed.
  // Only whitelisted objects can be proxied
  if(! canObserve(target)) {return target
  }
  // If it is already handled and stores values of Set, Map, WeakMap, WeakSet, use collectionHandlers collection handler, otherwise use baseHandlers basic handler
  const handlers = collectionTypes.has(target.constructor)
    ? collectionHandlers
    : baseHandlers
  // Here is the whole data response most always need a line of code, new Proxy
  observed = new Proxy(target, handlers)
  // Store the result of processing, mainly to avoid repeated processing
  toProxy.set(target, observed)
  // The same is to avoid repeated processing
  toRaw.set(observed, target)
  // The main thing here is to mark each time the agent, but what are the benefits of doing so
  if(! targetMap.has(target)) { targetMap.set(target,new Map()}// Return the proxied object
  return observed
}

/ / / / / / / / / / / / / / / / / / / / the following still don't understand / / / / / / / / / / / / / / / / / / / / / / /

// Check whether it can be changed
export function isReactive(value: any) :boolean {
  return reactiveToRaw.has(value) || readonlyToRaw.has(value)
}

// Check whether it is read-only
export function isReadonly(value: any) :boolean {
  return readonlyToRaw.has(value)
}


export function toRaw<T> (observed: T) :T {
  return reactiveToRaw.get(observed) || readonlyToRaw.get(observed) || observed
}

export function markReadonly<T> (value: T) :T {
  readonlyValues.add(value)
  return value
}

export function markNonReactive<T> (value: T) :T {
  nonReactiveValues.add(value)
  return value
}
Copy the code

The last

The level is limited, some of the annotations are their own guesses, there are mistakes and omissions hope we point out, thanks to the community and group to give me the answer to the big guy!