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!