Almost there, the picture is a bit of a clown, you can also look at other people’s more complete picture.
@mxin

preface

Long time no contact with VUE, in a few days ago to watch Youda live talk about some views on the source code, in order to better start VUE? Or do you want to learn internal framing ideas?

Domestic front end: the interview, the interview will ask.

In the overall environment seems to have been volume to as long as you are a developer, then it is necessary to learn the source code, whether you are an intern, or a fresh graduate, or many years of experience of the old front end.

If you stop rolling and don’t follow it, all of a sudden the stress will wear you down so much that you may find it hard to survive inside rolling, even if you’re right.

For more information, read the self-rated programmer anxiety chart written by the @Nuggets Mudslide boss.

There seems to be too much digression. Instead of complaining, it is better to calm down and learn some basic principles of Reactivity. I believe that after reading this article, you will have a deeper understanding of Vue 3 data response.

And the reason why
ReactivityModule, because its coupling degree is low, and is
vue3.0One of the core modules, cost-effective cost is very high.

Based on article

Before the beginning, if you do not know some high-level APIs of ES6, such as Proxy, Reflect, WeakMap, WeakSet,Map, Set, etc., you can browse the resource chapter by yourself. It is best to understand the previous knowledge points before re-watching.

Proxy

In @vue/reactivity, Proxy is the cornerstone of the entire schedule.

Only through Proxy objects, can the subsequent things in get and set methods be completed, such as dependency collection, effect, track, trigger, etc., which will not be discussed in detail here, but will be discussed in detail later.

If a student is eager and talented,
ES6Have a certain foundation, can jump directly to the principle of the article to watch and think.

Let’s start by handwriting a simple Proxy. HandleCallback in which write set, get two methods, and to intercept the current attribute value changes of data monitoring. First on the code:

Const user = {name: 'wangly19', age: 22, description: ' ' } const userProxy = new Proxy(user, { get(target, key) { console.log(`userProxy: ${key} ') if (target.hasOwnProperty(key)) return target[key] return {}}, set(target, key, key) value) { console.log(`userProxy: ${key}; ${key}; Value = ${value} ') let IsWriteSuccess = false if (target.hasOwnProperty(key)) {target[key] = value IsWriteSuccess = true  } return isWriteSuccess } }) console.log('myNaame', userProxy.name) userProxy.age = 23

The current set and get methods are triggered when we assign and print values, respectively.

This is so important that I won’t go into too much detail about the other properties and how to use them,

Reflect

Reflect is not a class, but a built-in object. New handles are similar to Proxy handles, and a lot of Object methods are added on this basis.

We won’t go into that here
ReflectIf you want to know more about the function, you can find the corresponding address in the following resources to learn. In this chapter mainly introduces the passage
ReflectSafe action objects.

The following are some examples of modifications to the User object for reference and may be used in the future.

Const user = {name: 'wangly19', age: 22, description: ' ' } console.log('change age before' , Reflect.get(user, 'age')) const hasChange = Reflect.set(user, 'age', 23) console.log('set user age is done? ', hasChange ? 'yes' : 'no') console.log('change age after' , Reflect.get(user, 'age')) const hasDelete = Reflect.deleteProperty(user, 'age') console.log('delete user age is done?', hasDelete ? 'yes' : 'none') console.log('delete age after' , Reflect.get(user, 'age'))

The principle of article

When you understand some of the previous knowledge, it is time to start the @vue/reactivity source parsing chapter. I’ll start with a simple idea of how to implement a basic reactivity. Once you understand the basics, you’ll collect dependency tracks and trigger updates for @vue/reactivity, as well as how effects actually work.

reactive

Reactive is the VUE3 API used to generate reference types.

Const user = reactive({name: 'wangly19', age: 22, description: ') const user = reactive({name: 'wangly19', age: 22, description: ') '})

So if you look inside the function, what is the reactive method doing?

Internally, it performs a read-only check on the target that is passed in. If you pass the target as a read-only proxy, it will be returned directly. Reactive normally returns the CreateReactive Object method value.

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

In CreateReactiveObject, all you do is add a proxy proxy for Target. This is the core of it. Reactive gets a proxy. Refer to the Proxy section for a simple example of how it works and see what CreateActiveObject does.

First, determine the type of the current target. If the target does not meet the requirements, simply throw a warning and return the original value.

if (! isObject(target)) { if (__DEV__) { console.warn(`value cannot be made reactive: ${String(target)}`) } return target }

Next judge whether the current object has been proxied and is not read-only, then itself is a proxy object, then there is no need to go to the proxy, directly return it as a return value, to avoid repeating the proxy.

if ( target[ReactiveFlags.RAW] && ! (isReadonly && target[ReactiveFlags.IS_REACTIVE]) ) { return target }

It’s not too hard to read this judgment code, but notice the condition in if () and see what it does. The most important thing CreateReactiveObject does is create a proxy for Target and record it in a Map.

What is interesting is that it calls a different Proxy Handle on the incoming target. So let’s take a look at what Handles does.

const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy

The type of handles

In the Object type, Object and Array are distinguished from Map,Set, WeakMap,WeakSet. They call different Proxy Handles.

  • baseHandlers.ts:Object & ArrayWill be called under this filemutableHandlersObject as aProxy Handle.
  • collectionHandlers.ts:Map.Set.WeakMap.WeakSetWill be called under this filemutableCollectionHandlersObject as aProxy Handle.
/** * @LineNumber 41 */ function targetTypeMap(rawType: string) {switch (rawType) {case 'Object': case 'Array': return TargetType.COMMON case 'Map': case 'Set': case 'WeakMap': case 'WeakSet': return TargetType.COLLECTION default: return TargetType.INVALID } }

It will be judged by the targetType returned by the New Proxy.

const proxy = new Proxy(
  target,
  targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)

Due to space constraints, the following only examples
mutableHandlersAs a reference for analysis. When understanding
mutableHandlersFor after
collectionHandlersIt’s just a matter of time.

Proxy Handle

Handles are called according to the Handle Type. Let’s see what the mutableHandlers do.

In the basics, we learned that proxies can receive a configuration object, and we demonstrated the property methods for get and set. If you don’t know what this means, you can look at Proxy MDN to see how mutableHandlers are defined in the Proxy MDN. If you don’t know what this means, you can see the Proxy MDN. What you need to understand here is that most of the data that you’re listening to as soon as you add or delete it, it’s going to end up in the corresponding return receipt channel.

Here, we use simple GET, SET to conduct a simple simulation example.

function createGetter () { return (target, key, receiver) => { const result = Reflect.get(target, key, receiver) track(target, key) return result } } const get = /*#__PURE__*/ createGetter() function createSetter () { return (target, key, value, receiver) => { const oldValue = target[key] const result = Reflect.set(target, key, value, receiver) if (result && oldValue ! = value) { trigger(target, key) } return result } }

“Get” is a track dependency collection, while “Set” is the mechanism that triggers the trigger. In Vue3, while trigger and track words are declared in our effect.ts, let’s take a look at what dependency collection and response triggering actually do.

Effect

For the entire Effect module, divide it into three sections to read:

  • effect: Side effect function
  • teack: Dependency collection, inproxyThe proxy datagetCalled when the
  • trigger: Trigger the response inproxyCalled when the agent data changes.

effect

Take a look at the use of effect and see that its main argument is a function. Inside the function, it performs some side effects logging and feature determination for you.

effect(() => {
    proxy.user = 1
})

What does the Vue Effect do?

Here, we first determine if the current parameter fn is an effect and, if so, replace the fn stored in RAW. Then do the CreateActiveEffect generation again.

export function effect<T = any>( fn: () => T, options: ReactiveEffectOptions = EMPTY_OBJ ): ReactiveEffect<T> { if (isEffect(fn)) { fn = fn.raw } const effect = createReactiveEffect(fn, options) if (! options.lazy) { effect() } return effect }

CreateActiveEffect pushes our effect onto the effectStack for a push operation, and then uses the ActiveEffect to access the currently executed effect and push it off the stack when it’s finished executing. Replace ActiveEffect with the new top of the stack.

The Proxy Handle and track and trigger functions will be triggered during the execution of the effect.

function createReactiveEffect<T = any>( fn: () => T, options: ReactiveEffectOptions ): ReactiveEffect<T> { const effect = function reactiveEffect(): unknown { if (! effect.active) { return options.scheduler ? undefined : fn() } if (! effectStack.includes(effect)) { cleanup(effect) try { enableTracking() effectStack.push(effect) activeEffect = effect return fn() } finally { effectStack.pop() resetTracking() activeEffect = effectStack[effectStack.length - 1] } } } as ReactiveEffect effect.id = uid++ effect.allowRecurse = !! options.allowRecurse effect._isEffect = true effect.active = true effect.raw = fn effect.deps = [] effect.options = options return effect }

Let’s take a look at a simplified version of Effect, where most of the code baggage aside, the following code is a lot cleaner.

function effect(eff) { try { effectStack.push(eff) activeEffect = eff return eff(... argsument) } finally { effectStack.pop() activeEffect = effectStack[effectStack.length - 1] } }

Track (Dependency Collection)

In Track, we do what we know as dependency collection, which adds the current ActiveEffect to the DEP, and we talk about relationships like this. It will have a one-to-many, one-to-many relationship.

It is also very clear from the code, first of all we will have a total targetMap which is a weakMap, the key is the target(the object of the agent), the value is a Map, called DepSMAP, which is used to manage the DEPS of each key in the current target, which is the side effect dependency, It’s known as depend. In Vue3 this is done through a Set.

TargetMap. Set (target, (depSMAP = new Map())); DepsMap. Set(key, (dep = new set()))). Finally, push the current ActiveEffect into the depps for dependency collection.

    1. intargetMapIn looking fortarget
    1. indepsMapIn looking forkey
    1. willactiveEffectSave todepThe inside.

This results in a one-to-many structure that holds all of the dependencies hijacked by the proxy.

function track(target: object, type: TrackOpTypes, key: unknown) { if (! shouldTrack || activeEffect === undefined) { return } let depsMap = targetMap.get(target) if (! depsMap) { targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key) if (! dep) { depsMap.set(key, (dep = new Set())) } if (! dep.has(activeEffect)) { dep.add(activeEffect) activeEffect.deps.push(dep) if (__DEV__ && activeEffect.options.onTrack) { activeEffect.options.onTrack({ effect: activeEffect, target, type, key }) } } }

Trigger (response)

When triggered, all you do is trigger the execution of the current response dependency.

First of all, we need to get the DEPS of all channels under the current key, so we can see that there is an Effects and Add function that does a very simple thing, which is to determine whether the depsMap attribute that is currently passed in needs to be added to the effects. The condition here is that the effect cannot be the current ActiveEffect and Effect.AllowRecurse to ensure that the current set key dependencies are executed.

const effects = new Set<ReactiveEffect>() const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => { if (effectsToAdd) { effectsToAdd.forEach(effect => { if (effect ! == activeEffect || effect.allowRecurse) { effects.add(effect) } }) } }

The most common scenario is the triggeropTypes action passed in the trigger and then execute the add method to add the applicable effect to the effects. Here @vue/reactivity does a lot of data on variation behavior, such as length variation.

You can then extract the data from depsMap according to different TriggerOpTypes and put it into Effects. The current effect is then executed through the run method, and through effects.forEach(run).

if (type === TriggerOpTypes.CLEAR) { // collection being cleared // trigger all effects for target depsMap.forEach(add) } else if (key === 'length' && isArray(target)) { depsMap.forEach((dep, key) => { if (key === 'length' || key >= (newValue as number)) { add(dep) } }) } else { // schedule runs for SET | ADD |  DELETE if (key ! == void 0) { add(depsMap.get(key)) } // also run for iteration key on ADD | DELETE | Map.SET switch (type) { case TriggerOpTypes.ADD: if (! isArray(target)) { add(depsMap.get(ITERATE_KEY)) if (isMap(target)) { add(depsMap.get(MAP_KEY_ITERATE_KEY)) } } else if (isIntegerKey(key)) { // new index added to array -> length changes add(depsMap.get('length')) } break case TriggerOpTypes.DELETE: if (! isArray(target)) { add(depsMap.get(ITERATE_KEY)) if (isMap(target)) { add(depsMap.get(MAP_KEY_ITERATE_KEY)) } } break case TriggerOpTypes.SET: if (isMap(target)) { add(depsMap.get(ITERATE_KEY)) } break } }

And what did Run do?

The first step is to determine if there is a scheduler for the current effect options. If there is a scheduler for the current effect options, use schedule to handle the execution, and if there is a scheduler for the current effect(), execute it directly.

if (effect.options.scheduler) {
      effect.options.scheduler(effect)
    } else {
      effect()
    }

To shorten it a little bit and look at the processing logic, it’s essentially taking the dependency of the corresponding key from the targetMap.

const depsMap = targetMap.get(target) if (! depsMap) { return } const dep = depsMap.get(key) if (dep) { dep.forEach((effect) => { effect() }) }

Ref

As we all know, ref is a reactive data declaration of Vue3 to a common type. Reactive value should be obtained through Ref. Value. Many people think Reactive is a simple reactive, but it is not.

In the source code, ref ends up calling a createRef method that returns an instance of refImpl inside. It is different from Proxy in that the dependency collection and response triggering of Ref are in the getter/setter. You can refer to the demo form in the figure and link to the address gettter/setter.

export function ref<T extends object>(value: T): ToRef<T> export function ref<T>(value: T): Ref<UnwrapRef<T>> export function ref<T = any>(): Ref<T | undefined> export function ref(value? : unknown) { return createRef(value) } function createRef(rawValue: unknown, shallow = false) { if (isRef(rawValue)) { return rawValue } return new RefImpl(rawValue, shallow) }

As shown in the figure, Vue calls the track collection dependency in the getter just like get in the proxy, and calls the trigger trigger after making the _value change in the setter.

class RefImpl<T> {
  private _value: T

  public readonly __v_isRef = true

  constructor(private _rawValue: T, public readonly _shallow = false) {
    this._value = _shallow ? _rawValue : convert(_rawValue)
  }

  get value() {
    track(toRaw(this), TrackOpTypes.GET, 'value')
    return this._value
  }

  set value(newVal) {
    if (hasChanged(toRaw(newVal), this._rawValue)) {
      this._rawValue = newVal
      this._value = this._shallow ? newVal : convert(newVal)
      trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
    }
  }
}

So what you should know by now:

  • proxy handleisreactiveThe principle ofrefThe principle isgetter/setter.
  • ingetIt’s called when it’s calledtrack.setIt’s called when it’s calledtrigger
  • effectIs the core of the data response.

Computed

Computed is usually used in two common ways. One is by passing in an object with set and get methods inside, which is the form of computedOptions.

export function computed<T>(getter: ComputedGetter<T>): ComputedRef<T>
export function computed<T>(
  options: WritableComputedOptions<T>
): WritableComputedRef<T>
export function computed<T>(
  getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
)

And inside there are two variables called getter/setter to store it.

When getTerorOptions is a function, it is assigned to the getter.

When getTerorOptions is an object, set and get are assigned to the setter and getter, respectively.

We then instantiate the ComputedRefImpl class as a parameter and return it as a return value.

let getter: ComputedGetter<T> let setter: ComputedSetter<T> if (isFunction(getterOrOptions)) { getter = getterOrOptions setter = __DEV__ ? () => { console.warn('Write operation failed: computed value is readonly') } : NOOP } else { getter = getterOrOptions.get setter = getterOrOptions.set } return new ComputedRefImpl( getter, setter, isFunction(getterOrOptions) || ! getterOrOptions.set ) as any

So what does ComputedRefImpl do?

The source code for calculating properties, in fact, mostly relies on the previous understanding of effect.

First, we all know that effect can pass a function and an object options.

Here, the getter is passed as a function argument, which is a side effect, and the lazy and scheduler are configured in the options section.

Lazy means that an effect isn’t executed immediately, whereas a scheduler is in the trigger that determines whether or not you passed a scheduler in, and then executes a scheduler method.

On the Computed Scheduler, the current _dirty is determined to be false. If it is, the _dirty is set to true, and the trigger is executed to trigger the response.

class ComputedRefImpl<T> { private _value! : T private _dirty = true public readonly effect: ReactiveEffect<T> public readonly __v_isRef = true; public readonly [ReactiveFlags.IS_READONLY]: boolean constructor( getter: ComputedGetter<T>, private readonly _setter: ComputedSetter<T>, isReadonly: boolean ) { this.effect = effect(getter, { lazy: true, scheduler: () => { if (! this._dirty) { this._dirty = true trigger(toRaw(this), TriggerOpTypes.SET, 'value') } } }) this[ReactiveFlags.IS_READONLY] = isReadonly }

In the getter/setter, you do something different with _value.

First, in get value, determine whether._dirty is currently true, and if so, execute the cached effect and store its return into _value, and execute track for dependency collection.

Next, in the set value, the _setter method is called to reset the value.

get value() {
    // the computed ref may get wrapped by other proxies e.g. readonly() #3376
    const self = toRaw(this)
    if (self._dirty) {
      self._value = this.effect()
      self._dirty = false
    }
    track(self, TrackOpTypes.GET, 'value')
    return self._value
  }

  set value(newValue: T) {
    this._setter(newValue)
  }

The resource reference

The following are some reference resources, interested partners can see

  • WeakMap of ES6 series
  • The Proxy and Reflect
  • Vue Mastery
  • Vue Docs
  • The @vue/reactivity of Vue3 is introduced in React to achieve responsive state management

conclusion

If you are using Vue, it is highly recommended that you debug this section to the end, it will definitely help you to write code. VUE3 is in full swing, there are already teams working on the project in the production environment, and the ecology of the community is slowly developing.

@vue/reactivity is not very difficult to read, there are many excellent tutorials, there are certain basic work and code knowledge can be gradually understood down. Personally, I don’t really need to understand it thoroughly, to understand what every line of code means, but to understand the core idea, to learn the framework concept and the way some framework developers write code. This is all knowledge that you can draw on and incorporate into your own.

For one that has been transferred to
ReactThe front end of the ecosystem is read
VueThe source code is actually more to enrich their knowledge in thinking, rather than to interview and read. Just as you recite not to test, but to learn knowledge. In the present environment, it is difficult to do these things, calm down to concentrate on understanding a knowledge is better than reciting a few surface scripture.