The author:Xu เธˆ เธธ เนŠ เธš, shall not be reproduced without authorization.

preface

Vue.js 3.0 (Vue3 for short) was officially released on September 18, 2020, and the Vue3 Beta was released back in April. I believe some students have already started to experience some. Or have already seen the official document, English is not good students to click here, this is a Chinese document. ๐Ÿ˜‚ ๐Ÿ˜‚ ๐Ÿ˜‚ ๐Ÿ˜‚ ๐Ÿ˜‚ ๐Ÿ˜‚

Want to think you will certainly have a certain understanding of the Vue2 source code, or the implementation of the core function, this article is in my way to learn Vue2, to communicate with you, learn Vue3 source code, the current version is 3.0.2. If there are any errors or omissions, please correct and supplement them.


Project directory

The directory structure

Let’s review the Vue2 source directory

Exercises โ”€โ”€ SRC โ”€ compiler # Exercises โ”€โ”€ Core # vue Exercises โ”€โ”€ Platforms โ”€โ”€ Server # Exercises โ”€โ”€ SFC # vue Single file component Exercises โ”€โ”€ Shared # Common method for contentCopy the code

Take a look at the directory structure in the Vue3 source code

โ”œโ”€โ”€ Compiler-core Exercises โ”€โ”€ Compiler-dom Exercises โ”€โ”€ Compiler-sFC Exercises โ”€โ”€ simpler-ssr Exercises โ”€โ”€ Activity Exercises โ”€โ”€ Runtime-core Exercises โ”€โ”€ Run-time Heavy Guitar Exercises - - Vue Heavy Guitar Exercises - -...Copy the code

Choose a few to talk about these module functions

  • Compiler-core: Platform-independent compilation modules, such as the base BasecomCompile template file, which baseParse generates as an AST
  • Compiler-dom: Compiler-core based module for the browser. You can see that it overwrites Complie and Parse based on BasecomCompile and baseParse
  • Compiler-sfc: Is used to compile vUE single-file components
  • Compiler-ssr: server-side rendering related
  • Reactivity: VUE independent responsive module
  • Run-time core: Also a platform-independent base module with vUE apis and renderer for the virtual DOM
  • Runtime-dom: Runtime based on runtime-core for browsers
  • Vue: Import and export Runtime-core, and compile methods

It can be seen that the Vue3 module is clearly split and the modules are relatively independent. We can refer to the reactivity module separately or refer to compiler-sFC to use it in our own developed plugin, such as Vue-loader and Vite.

monorepo

This is because Vue3 uses Monorepo as a way of managing project code. Unlike Vue2 code management, it manages multiple packages in a single repo, each with its own type declaration and unit tests. Packages can also be released independently, and are generally easier to maintain, release, and read.

Start at the entrance

Create a Vue instance

Recall that Vue2 creates an instance of b Vue

import Vue from 'vue';
import App from './App.vue';

const vm = new Vue({
  / * option * /
}).$mount('#app');
Copy the code

Create an application instance in Vue3

import { createApp } from 'vue';
import App from './App.vue';

const app = Vue.createApp({
  / * option * /
});

const vm = app.mount('#app');
Copy the code

As you can see, the difference between the two is that each Vue application in Vue2 creates a new Vue instance with the new Vue constructor. In Vue3, each Vue application is started by creating a new application instance with the createApp function.

The essence is that there is no difference, but there are other positive effects. Following the example above, suppose we need to register a global component, or change the global configuration.

/* How to do Vue2 */
Vue.component('my-component', {
  / * option * /
});
Vue.directive('my-directive'{}); Vue.mixin({/ *... * /
});

/* Vue3 */
const app = Vue.createApp({
  / * option * /
});
app.component('my-component' / * * / components); // Each method is chaining
app.directive('my-directive' / * * / instruction);
app.mixin();
Copy the code

Vue2 uses Vue’s API and can change Vue globally, which is very convenient, but it is not friendly for the same page through the same Vue constructor instance of multiple apps. For example,

const app1 = new Vue({}).$mount('#app1');
const app2 = new Vue({}).$mount('#app2');
Copy the code

Vue3 avoids this problem by changing only the currently configured application instance.

The source entry

Import {createApp} from ‘vue’; This sentence begins to learn the source code.

In the source code of the vue module, we can see that the vue module actually do the introduction of run-time dom, complier. Continue looking for createApp in run-time dom.

export const createApp = ((. args) = > {
  // You can see that the real createApp method is on the renderer property
  constapp = ensureRenderer().createApp(... args)// ...
  const { mount } = app
  app.mount = (containerOrSelector: Element | string): any= > {
    // ...
  }

  return app
}) as CreateAppFunction<Element>

/** * ensureRenderer -- or "ensurer" -- is the new ensurer; It is possible to import reactive modules, not createApp, but createRenderer. It is possible to shake up the run-time core module from tree-shaking when packaging it. * * /
function ensureRenderer() {
  return renderer || (renderer = createRenderer<Node, Element>(rendererOptions))
}

Copy the code

You can find the definition of createApp in runtime-dom/index.ts. The implementation of createApp is divided into three steps: create the app instance, override the mount method, return the app instance.

Next, let’s look at createRenderer in the Run-time core module that exports this method, and find the real createApp

/ / 1
export { createRenderer } from './renderer'

/ / 2
export function createRenderer (options) {
  return baseCreateRenderer(options)
}

/ / 3
function baseCreateRenderer(options: RendererOptions, createHydrationFns? :typeof createHydrationFunctions
) {
  const {
    insert: hostInsert,
    remove: hostRemove,
    patchProp: hostPatchProp,
    forcePatchProp: hostForcePatchProp,
    createElement: hostCreateElement,
    createText: hostCreateText,
    createComment: hostCreateComment,
    setText: hostSetText,
    setElementText: hostSetElementText,
    parentNode: hostParentNode,
    nextSibling: hostNextSibling,
    setScopeId: hostSetScopeId = NOOP,
    cloneNode: hostCloneNode,
    insertStaticContent: hostInsertStaticContent
  } = options

  // ...

  return {
    render,
    hydrate,
    createApp: createAppAPI(render, hydrate)
  }
}

/ / 4
export function createAppAPI(render, hydrate) {
  return function createApp(rootComponent, rootProps = null) {
    // ...
    const app: App = (context.app = {
      _uid: uid++,
      _component: rootComponent as ConcreteComponent,
      _props: rootProps,
      _container: null._context: context,
      version,

      use (plugin) {
        // ...
        return app
      },
      mixin(mixin: ComponentOptions) {
        // ...
        returnapp }, mount(rootContainer: HostElement, isHydrate? : boolean): any {if(! isMounted) {const vnode = createVNode(
            rootComponent as ConcreteComponent,
            rootProps
          )

          vnode.appContext = context

          if (isHydrate && hydrate) {
            // ...
          } else {
            render(vnode, rootContainer)
          }
          isMounted = trueapp._container = rootContainer ; (rootContaineras any).__vue_app__ = app

          returnvnode.component! .proxy } },// ...}}Copy the code
  • Note: I omitted a lot of code unrelated to the creation of pp instances. I think it is clearer to see the source code than to read it line by line.

After four tries we finally found the createApp method in Run-time core/apiCreateApp, and the app instance. You can see that the API for the Vue constructor in Vue2 is basically the same as for the app instance. App instances such as use, Component, and so on are returned to the app instance, which supports chained writing.

From the createApp method call to the creation of the app instance, we can see how the Runtime-dom module builds a virtual DOM renderer for the browser based on runtime-core.

/** * Inside the runtimedom, call the runtimecore createRenderer method * and pass in rendererOptions, which contains the browser's DOM API. Props * for createElement, insertBefore, etc. Check out runtimedom/nodeops.ts. * * * /
function ensureRenderer() {
  return renderer || (renderer = createRenderer<Node, Element>(rendererOptions))
}

/** * pass in endererOptions for different environments to generate render for different environments
function baseCreateRenderer(options: RendererOptions, createHydrationFns? :typeof createHydrationFunctions
) {
  // These variables are ultimately used for patch in Redner
  const {
    insert: hostInsert,
    remove: hostRemove,
    patchProp: hostPatchProp,
    createElement: hostCreateElement,
    // ...
  } = options

  // Generate render for the browser environment
  const render: RootRenderFunction = (vnode, container) = > {
    if (vnode == null) {
      if (container._vnode) {
        unmount(container._vnode, null.null.true)}}else {
      patch(container._vnode || null, vnode, container)
    }
    flushPostFlushCbs()
    container._vnode = vnode
  }

  return {
    render,
    hydrate,
    // Pass this parameter to createApp, which is used by the mount in the app instance.
    createApp: createAppAPI(render, hydrate)
  }
Copy the code

Let’s go ahead and look at the createApp method in Runtime-core /apiCreateApp

export function createAppAPI(render, hydrate) {
  return function createApp(rootComponent, rootProps = null) {
    // ...
    const app: App = (context.app = {
      // ...
      /** * We create an app instance in the project and mount it to a node * we end up here with the app component as rootComponent and render as the browser environment's renderer. * * * /mount(rootContainer: HostElement, isHydrate? : boolean): any {if(! isMounted) {const vnode = createVNode(
            rootComponent as ConcreteComponent,
            rootProps
          )
          vnode.appContext = context

          if (isHydrate && hydrate) {
            // ...
          } else {
            render(vnode, rootContainer)
          }
          isMounted = trueapp._container = rootContainer ; (rootContaineras any).__vue_app__ = app

          returnvnode.component! .proxy } }// ...}}Copy the code

Of course, we in the project. Mount (‘#app’) does not directly execute the app instance mount, in this case the runtime core mount method is platform independent. Run-time dom actually overrides mount, also for the browser environment.

However, I won’t go into this part of this article because the mount process is basically creating a Vnode, rendering it, and generating the real DOM, and it uses the new responses from Vue3, so let’s take a look at the reactivity module, which can be used on its own without any additional burden. Dig yourself a hole, will say later -. – |)


Vue3’s response

Proxy

We know that Vue2 hijks data changes internally through the Object.defineProperty API, traversing objects in the data function in depth, setting getters and setters for every property in the Object.

Triggering the getter will do a dependency collection through the Dep class, collecting the current dep. target, which is the watcher.

When the setter is triggered, the update dispatch operation is performed, and dep. Notify is executed to notify the collected watcher updates, such as computed Watcher, User watcher, and render Watcher.


Vue3 reconstructs the responsive part with Proxy, and replaces watcher with effect side effect function.

Track () is executed in the Proxy get handle to track collection dependencies (collect activeEffect, i.e., effect),

Set Handle executes trigger() to trigger the response (to perform the collected effect)

Independent response

As mentioned many times before, reactivities can be used independently, such as in Node.

// index.js
const { effect, reactive } = require('@vue/reactivity');
Reactive data. Reactive data. Reactive data. Reactive data
const obj = reactive({ num: 1 });

// Effect defines the side effect function
effect(() = > {
  console.log(obj.num);
});

// Modify num, trigger to trigger the response and perform effect
setInterval(() = > {
  ++obj.num;
}, 1000);
Copy the code

Nodeindex.js, run this script and you can see that the console will print incrementally all the time.

reactive

Reactive, effect @vue/reactivity We will only focus on what we are concerned about at the moment, and then comb the rest of the methods after the main flow in this direction has gone through.

Let’s start with a brief introduction to writing the ReactiveFlags enumeration, because it will be used later

export const enum ReactiveFlags {
  SKIP = '__v_skip'.// Objects whose property value is true will skip the proxy
  IS_REACTIVE = '__v_isReactive'.// Get whether it is responsive
  IS_READONLY = '__v_isReadonly'.// Whether it is read-only
  RAW = '__v_raw' // This property is applied to the original object
}
Copy the code

Let’s move on to the reactive method.

export function reactive(target: object) {
  // If the data is read-only and responsive, it will return itself
  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
    return target
  }
  return createReactiveObject(
    target, / / object
    false.// Read-only or not
    mutableHandlers, // proxy handle
    mutableCollectionHandlers  // Proxy handle for collection data)}function createReactiveObject(target: Target, isReadonly: boolean, baseHandlers: ProxyHandler
       
        , collectionHandlers: ProxyHandler
        ) {
  if(! isObject(target)) {// Not an object directly returned
    return target
  }

  // If it is already a reactive object, return it directly, unless readonly is applied to the reactive object
  if( target[ReactiveFlags.RAW] && ! (isReadonly && target[ReactiveFlags.IS_REACTIVE]) ) {return target
  }

  // A cache map where key is the target object and value is the responsive object
  // If this object has already created a responsive object, read back from the cache map
  const proxyMap = isReadonly ? readonlyMap : reactiveMap
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }

  // The type is not Object array Map Set weakMap WeakSet is outside the whitelist, and the proxy is not created
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }

  // Use proxy to create reactive objects
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  // Save to the cache map
  proxyMap.set(target, proxy)
  return proxy
}
Copy the code

What actions are being hijacked in baseHandlers?

// reactivity/baseHandlers.ts => mutableHandlers
// In this case, we select the handlers of the normal object
export const mutableHandlers: ProxyHandler<object> = {
  get, // The handler that accesses the object properties
  set, // Set the handler for the object properties
  deleteProperty, // Delete the object property handler
  has, // Handler for the in operator
  ownKeys // Handler for getOwnPropertyNames, getOwnPropertySymbols, Keys, etc
};
Copy the code

The advantage of using Proxy is that I believe many people know something about it even if they haven’t read the source code. For example, Proxy compensates for Object. DefineProperty requiring recursive objects, setting setters and getters for every property, no hijacking of other operations, arrays requiring hacks, handling new properties requiring additional methods, Map, Set, weakMap and other data structures cannot respond.

conclusion

Some of the above are optimizations brought about by using a Proxy, and we’ll look at other optimizations in the code implementation later. Reactive object. Reactive object. Reactive object. Reactive object. Reactive object. Reactive object. Reactive object. Finally, we assign the responder object returned to obj.

effect

Follow the order of the examples above and look at this sentence again

  effect(() = > {
    console.log(obj.num);
  });
Copy the code

We passed a function () to effect => {console.log(objectix.num); }, which accesses the num property of the reactive object obj. Let’s take a look at effect’s source code.

export function effect<T = any> (fn: () => T, options: ReactiveEffectOptions = EMPTY_OBJ) :ReactiveEffect<T> {
  if (isEffect(fn)) {
    // 1. If fn has effect, it refers to the original function. You can see the raw and _isEffect definitions in createReactiveEffect below
    fn = fn.raw
  }
  // 2. Create a reactive side effect function
  const effect = createReactiveEffect(fn, options)
  if(! options.lazy) {// 3. Execute effect, which has a lazy property like computed Watcher. If true, it is not executed immediately.
    effect()
  }
  // 4. Return effect wrapped in fn
  return effect
}

let uid = 0

const effectStack = [] // Effect stack, remember Vue2 global Watcher stack?
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)) {// This is an optimization that removes all the deps in the deps. When you add dep to the deps in the deps track, you remove unnecessary dependencies
      cleanup(effect)
      try {
        // To enable collection, set this variable shouldTrack to true
        enableTracking()
        // set activeEffect and execute the original function
        effectStack.push(effect)
        activeEffect = effect
        return fn() // Here we execute the original function, which refers to the value of the reactive object we wrote in the function, and triggers the get handler of the reactive object
      } finally {
        // finally off the stack, stop the collection, activeEffect refers to the previous effect, here is the effect with nested relationship
        effectStack.pop()
        resetTracking()
        activeEffect = effectStack[effectStack.length - 1]}}}as ReactiveEffect
  // Here are the properties related to effecteffect.id = uid++ effect.allowRecurse = !! options.allowRecurse effect._isEffect =true
  effect.active = true
  effect.raw = fn
  effect.deps = [] // Effect is bidirectional dependent on DEP
  effect.options = options
  return effect
}

function cleanup(effect: ReactiveEffect) {
  / / deps is Set with an array of the Set of data structures [characters (...), Set2 (...),... Each Set is the DEP in the targetMap
  const { deps } = effect
  if (deps.length) {
    for (let i = 0; i < deps.length; i++) {
      deps[i].delete(effect)
    }
    deps.length = 0}}Copy the code

conclusion

So we know that executing effect(fn) in our example creates, executes, and returns an effect function. When executing this effect function, we turn on collection, push it into the global effectStack, and point the global activeEffect pointer to ourselves. And executes the passed FN, which triggers OBj’s GET handler. After fn is executed, the stack is cancelled, the collection is stopped, and the activeEffect points to the last effect on the stack.

Get, dependent collection

If obj’s GET handler is triggered, let’s take a look at the source code for GET.

function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    if (key === ReactiveFlags.IS_REACTIVE) {
      return! isReadonly }else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (
      key === ReactiveFlags.RAW &&
      receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)
    ) {
      return target
    }
    // The ReactiveFlags enumeration is used to retrieve the values specified in the ReactiveFlags enumeration. It is used to retrieve the values specified in the ReactiveFlags enumeration

    const targetIsArray = isArray(target)
    // The ['includes', 'indexOf', 'lastIndexOf'] array may change, and the result of this method may also change, so get is special
    // For example, when arr.includes('xx') is executed, each subscript of the arR array will be tracked
    if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }

    // Return the value using the Reflect map
    const res = Reflect.get(target, key, receiver)

    if (
      isSymbol(key)
        ? builtInSymbols.has(key as symbol)
        : key === `__proto__` || key === `__v_isRef`
    ) {
      // Judge the original method, return directly, do not track
      return res
    }

    if(! isReadonly) {// Track relies on collection operations
      track(target, TrackOpTypes.GET, key)
    }

    if (shallow) {
      // This is for shallow responses, such as the shallowReactive method
      return res
    }

    if (isRef(res)) {
      For example, num = ref(0) will create a responsive object with a value of 0. IsRef will determine if it is a value of ref
      constshouldUnwrap = ! targetIsArray || ! isIntegerKey(key)return shouldUnwrap ? res.value : res
    }

    if (isObject(res)) {
      // Subobjects are also recursively hijacked, but there is a point of optimization compared to Vue2.
      // In Vue2, if the property is still an array of objects, then immediately iterate over the child objects to do the hijacking
      // Vue3 accesses the property, finds that the value is an object, and then converts it into a responsive object

      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}
Copy the code

The get operator hijacks the value of the ReactiveFlags enumeration, evaluates and processes the special methods in the array separately, and then evaluates them using Reflect as a partner. Track again to do dependent collection of related things, and finally return the results. Let’s take a look at Track.

// ./effect.ts

// First look at the two variables used
activeEffect // Similar to the dep. target in Vue2, a watcher. This represents the currently active effect
shouldTrack // To determine whether the current collection should be done, effect's internal execution begins by setting this variable to true
targetMap // Take the original object as the key, and the value is also a weakMap. The map takes the attribute name key and the effect set as value
export 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()))}// All of this is cached to form a targetMap-like data structure
  /** * targetMap = { * [target]: { * [key]: new Set([ effect,... ] ) *} *} **/

  if(! dep.has(activeEffect)) {// when effect executes, point activeEffect to itself
    dep.add(activeEffect)
    // The currently activated effect will also store the deP set. This is in conjunction with the cleanup method in effect to clear unwanted dependencies
    activeEffect.deps.push(dep)
  }
}
Copy the code

Activeeffe.deps. push(dep) cleanup of unneeded dependencies

// Example 2: one more count attribute
const obj = reactive({num: 1.count: 0});

effect(() = > {
  if (obj.num === 1) {
    ++obj.num
    // We only access count if num is 1
    console.log('count', obj.count)
  }
  console.log('num', obj.num)
},);

setTimeout(() = > {
  // Since we first accessed the count, we changed the count to perform the effect
  console.log('First change count')
  obj.count = 2
}, 1000);

setTimeout(() = > {
  // The last time effect was executed, all dependencies were cleaned up, but because num was 2
  // The function does not access count internally. It does not track count, so it does not reactivate effe.deps.push (dep)
  // So changing count does not perform effect
  console.log('Change count second time')
  obj.count = 3
}, 1000);

setTimeout(() = > {
  // Num is accessed every time, so it triggers the response normally
  console.log('Change num for the third time')
  obj.num = 3
}, 1000);
Copy the code

In this example, the first time effect is executed automatically, the effect. Deps value will be [dep, dep], which is the dep set of num and the dep set of count.

Then change count to trigger effect. Run cleanup of num and count dep sets first.

When you do fn, because you’re only accessing num, effect is added to the DEp of num, but not to count. Effect. Deps will only push the DEp of num.

So when you change the count later, it won’t trigger the effect.

conclusion

Finally, going back to the main process, in our initial example we are passing in fn access obj.num, triggering the get handler, which will get the value 1 by refling. get, then track our NUM property, collect effect, Finally, the structure of the targetMap will look like this

targetMap = {
  [obj]: {
    'num' : new Set([effect])
  }
}
Copy the code

Set, dispatch updates

The previous section relied on the collection process and the completion. Where we say that changing NUM triggers effect execution, which is essentially sending updates, which is executing set Handler. Take a look at the set Handler source code

// ./baseHandlers.ts

function createSetter(shallow = false) {
  return function set(target: object, key: string | symbol, value: unknown, receiver: object) :boolean {
    // 1. Set the old value first
    const oldValue = (target as any)[key]
    if(! shallow) { value = toRaw(value)// toRaw is to fetch the original object. If value is a reactive object, then we will fetch the original object
      if(! isArray(target) && isRef(oldValue) && ! isRef(value)) {// If the old value is the Ref response object and the updated value is not, then update the value attribute of the old response object without executing the trigger here
        oldValue.value = value
        return true}}else {
      // in shallow mode, objects are set as-is regardless of reactive or not
    }
    
    // 2. Check whether the current set key exists on the target
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)

    // 3. Reflect. Set evaluation
    const result = Reflect.set(target, key, value, receiver)

    // In this case, reflect. set will come in again after the original data is modified
    if (target === toRaw(receiver)) {
       // 4. If the current key is an add triger or a set trigger, the set will have an oldvalue
      if(! hadKey) { trigger(target, TriggerOpTypes.ADD, key, value) }else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}
Copy the code

When you have read the logic of the set, it is roughly marked as 4 steps in the comment. Let’s take a look at the final step of the trigger

export function trigger(target: object, type: TriggerOpTypes, key? : unknown, newValue? : unknown, oldValue? : unknown, oldTarget? :Map<unknown, unknown> | Set<unknown>
) {
  // Select depsMap from targetMap stored in track
  const depsMap = targetMap.get(target)

  if(! depsMap) {// No dependencies, return directly and do not trigger subsequent effect execution
    return
  }

  const effects = new Set<ReactiveEffect>()
  const add = (effectsToAdd: Set<ReactiveEffect> | undefined) = > {
    if (effectsToAdd) { // This is the Set containing the effects
      effectsToAdd.forEach(effect= > {
        if(effect ! == activeEffect || effect.allowRecurse) {// The add method collects all effects into the effects collection
          effects.add(effect)
        }
      })
    }
  }

  // ...
  add(depsMap.get(key)) // Put the deP set into effects
  // ...

  const run = (effect: ReactiveEffect) = > {
    if (effect.options.scheduler) {
      // A scheduler that can sort, deduplicate, and execute asynchronously in nextTick
      // We can also customize this scheduler
      effect.options.scheduler(effect)
    } else {
      // Otherwise, execute directly
      effect()
    }
  }

  // Start the execution
  effects.forEach(run)
}
Copy the code

conclusion

In this example, we modify the NUM, trigger the set handler, and fetch the DEP corresponding to num from the targetMap. Add the effect from DEP to the larger set of effects using the add method. Finally, the run method is executed to iterate over the effect in the effects.

conclusion

This is the end of Vue3. Because of beginners, we can not do everything, also can not understand the role of every line of code, can only grasp the most concerned, the most basic process. Like a tree, we can’t move on to other branches until we know the basic trunk.

For example, there are a lot of other apis in the reactive analysis, such as refs, shallowReactive, readonly shallowReadonly, etc., but I think we need to understand the whole reactive side effect function through reactive analysis. It’s a lot easier to look back at the API.

Ps: This time we understand the basic response, you can learn Vue3 render logic, composition API, etc., later will also write down the process of learning other modules, there are wrong, also please correct, thank you!