Writing in the front

Vue3 has been around for a while now and has been particularly optimized compared to Vue2. But where is the specific good, in addition to developers with cool, the framework of the underlying optimization we need to study the source code to have personal experience.

This paper mainly compares Vue3 and Vue2 at the source level, thinking and summarizing what optimization the new Vue3 has done, and what is good about these optimization.

Ps: Click on the portal to organize the source code of Vue2

Note: some of the titles in the article are underlined blue method names. These methods have corresponding hyperlinks. Click to jump to the corresponding file location in the source code

1. Initialization

Compared with Vue2, Vue3 is a reconstruction and uses a multi-module architecture, which is divided into three modules: Compiler, Reactivity and Runtime

  • compiler-core
    • compiler-dom
  • runtime-core
    • runtime-dom
  • reactivity

In the future, custom rendering only needs to be extended based on compiler and Runtime core modules

This led to the concept of the Renderer, which stands for renderer, being the entry point to the application, coming from the Runtime module

Under the Runtime module, we can extend the rendering rules of any platform. Currently, we will study the entry of the Web platform is the Runtime-DOM.

The method of initialization also changed, creating the page application using the createApp method on the Vue instance, so we started with createApp

1.1. Initialization of Vue

1.1.1. createApp

  • role

    • To obtainThe renderer(renderer),The rendererthecreateAppcreateApp object
    • extensionmountmethods
  • The core source

    const createApp = ((. args) = > {
      constapp = ensureRenderer().createApp(... args);const { mount } = app;
      app.mount = (containerOrSelector: Element | ShadowRoot | string) :any= > {
        const container = normalizeContainer(containerOrSelector);
        if(! container)return;
    
        // Clear the DOM contents before mounting
        container.innerHTML = ' ';
        // Call the original mount method to mount it
        const proxy = mount(container);
    
        return proxy;
      };
    
      return app;
    }) as CreateAppFunction<Element>;
    
    function ensureRenderer() {
      // Return the singleton renderer
      return renderer || (renderer = createRenderer<Node, Element>(rendererOptions));
    }
    Copy the code

    The createApp is currently one that is ensureRenderer and is intended to be a Web platform-based renderer. The “current” ensureRenderer is one of the createApp ensureRenderer and will be specified later.

    RendererOptions is a web platform-specific way of manipulating DOM and properties.

1.1.2. createRenderer

  • role

    • Through the parameteroptionscreateplatformtheClient renderer
  • The core source

    function createRenderer<HostNode = RendererNode.HostElement = RendererElement> (
      options: RendererOptions<HostNode, HostElement>
    ) {
      return baseCreateRenderer<HostNode, HostElement>(options);
    }
    Copy the code

    BaseCreateRenderer is called again here to create the client renderer, and you can see below the current file that there is another method called createHydrationRenderer, which also calls baseCreateRenderer, which creates the server renderer

1.1.3. baseCreateRenderer

  • role

    • Returns the real platform renderer based on platform operation parameters
  • The core source

    function baseCreateRenderer(options: RendererOptions, createHydrationFns? :typeof createHydrationFunctions
    ) :any {
    
      Insert, remove, patchProp, etc
      const{... } = options// Next comes the many component rendering and diff methods
      const patch = (n1, n2, container, ...) = >{... }const processElement = (n1, n2, container) = >{... }const mountElement = (vnode, container, ...) = >{... }const mountChildren = (children, container, ...) = > {...}
      ...
    
      const render: RootRenderFunction = (vnode, container) = > {
        if (vnode == null) {
          if (container._vnode) {
            unmount(container._vnode, null.null.true); }}else {
          // Initialization and updates go here, similar to vue2's __patch__
          patch(container._vnode || null, vnode, container);
        }
        container._vnode = vnode;
      };
    
      return {
        render,
        hydrate,
        createApp: createAppAPI(render, hydrate),
      };
    }
    Copy the code

    The renderer includes the render, Hydrate, and createApp methods,

    This step is very important, and it is likely that future cross-platform development based on Vue3 will follow this pattern.

    The options parameter is used to deconstruct the platform-based manipulation of dom and attributes, which is used to create real render and update functions. One of the things to watch out for is Patch, because patch is not only responsible for rendering and updating, but also for future initialization components through this portal to ⭐.

    The Render method is similar to vm._update of vue2, responsible for initialization and update

    Since baseCreateRenderer is a method that is over 1800 lines long, you can only focus on the renderer that is eventually returned when you initialize it,

    The last createApp is created by the factory function createAppAPI

1.1.4. createAppAPI

  • role

    • Through the parameterrenderandhydratecreateplatformthecreateAppMethod,createAppUsed to create the realApp (Vue) instance
  • The core source

    function createAppAPI<HostElement> (render: RootRenderFunction, hydrate? : RootHydrateFunction) :CreateAppFunction<HostElement> {
      return function createApp(rootComponent, rootProps = null) {
        const app = {
          use(plugin: Plugin, ... options:any[]){ plugin.install(app, ... options);return app;
          },
    
          mixin(mixin: ComponentOptions) {
            context.mixins.push(mixin);
            return app;
          },
    
          component(name: string, component? : Component):any {
            context.components[name] = component;
            return app;
          },
    
          directive(name: string, directive? : Directive) {
            context.directives[name] = directive;
            returnapp; }, mount(rootContainer: HostElement, isHydrate? :boolean) :any{... },unmount() {
            if (isMounted) {
              render(null, app._container); }},provide(key, value) {
            context.provides[key as string] = value;
            returnapp; }};return app;
      };
    }
    Copy the code

    Remember, I reminded you that there are two createApp methods

    • inruntime-coreModule: create realApp (Vue) instance
    • inruntime-domIn the module: Passruntime-coreModule to createRenderer for web platform, the use ofThe renderergetThe instance

    CreateApp internally defines a number of methods on instances: use, mixin, Component, Directive, mount, unmount, provide. Those familiar with Vue2 may notice that static methods are now instance methods, and almost every method returns an app object, so it can be called chained, like this

    const app = createApp({
      setup() {
        const state = reactive({
          count: 0});return { state };
      },
    })
      .use(store)
      .directive(transfer)
      .mixin(cacheStore)
      .mount('#app');
    Copy the code

    When this step is complete, the createApp method with the renderer is also available, and the whole renderer is returned to the Runtime-dom module. Then create the app instance through the renderer, extend the mount method of the instance, and then enter the rendering stage of the instance.

    Mount is the entrance of the rendering stage core source code as follows:

    mount(rootContainer: HostElement, isHydrate? :boolean) :any {
      if(! isMounted) {// Initialize the virtual DOM tree
        const vnode = createVNode(rootComponent as ConcreteComponent, rootProps);
        if (isHydrate && hydrate) {
          // Server render
          hydrate(vnode as VNode<Node, Element>, rootContainer as any);
        } else {
          // Client render
          render(vnode, rootContainer);
        }
        return vnode.component!.proxy;
      }
    }
    Copy the code

    The unextended mount method of an app instance is equivalent to Vue2’s updateComponent and does two things:

    1. Get the virtual DOM,

    2. Convert the virtual DOM to the real DOM using the Render method

      • The core source

        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;
        };
        Copy the code

        The render method is particularly similar to vm._update of Vue2, which is the entry point for initial rendering and component update. Patch is called for both. Since there is no old virtual DOM for the first rendering, n1 is null

1.1.5. patch

  • role

    • Initialize and update the component according to the type of the virtual DOM, and finally convert the virtual DOM into the real DOM. (PS: Component includes browser host component and custom component, same below)
  • The core source

    const patch: PatchFn = (
        n1,// The old virtual DOM
        n2,// New virtual DOM
        container,
        anchor = null,
        parentComponent = null,
        parentSuspense = null,
        isSVG = false,
        optimized = false
      ) = > {
        // Type of the new node
        const { type, ref, shapeFlag } = n2
        switch (type) {
          case Text:
            ...
            break
          case Comment:
            ...
            break
          case Static:
            ...
            break
          case Fragment:
            ...
            break
          default:
            if (shapeFlag & ShapeFlags.ELEMENT) {
              ...
            } else if (shapeFlag & ShapeFlags.COMPONENT) {
              processComponent(
                n1,
                n2,
                container,
                anchor,
                parentComponent,
                parentSuspense,
                isSVG,
                optimized
              )
            }
        }
      }
    Copy the code

    The reason for the component initialization and update is that Vue3’s patch is different from Vue2’s __patch__, which is only responsible for rendering, so we can say it is the rendering of the component. However, the function triggered by Vue3’s patch in the rendering stage includes not only the rendering of the component, but also the initialization stage of the component

    Since the new virtual DOM (n2) passed in at initialization is the argument to the developer’s call to createApp, it is judged to be an object type and will be treated as a custom component, so the processComponent method is executed next

    The core source code is as follows:

    const processComponent = (
      n1: VNode | null,
      n2: VNode,
      container: RendererElement,
      anchor: RendererNode | null,
      parentComponent: ComponentInternalInstance | null,
      parentSuspense: SuspenseBoundary | null,
      isSVG: boolean,
      optimized: boolean
    ) = > {
      if (n1 == null) {
        if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
          ...
        } else {
          // Initialize rendermountComponent(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized); }}else {
        // Component updateupdateComponent(n1, n2, optimized); }};Copy the code

    Since the old virtual DOM passed in for the first rendering is null, the mountComponent method is executed

1.1.6. mountComponent

  • role

    • Initialize theCustom Components
      • Creating a component instance
      • Installing components (that is, component initialization)
      • Install side effects: Finish rendering and define update functions
  • The core source

    const mountComponent: MountComponentFn = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) = > {
      // 1. Create a component instance
      const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
        initialVNode,
        parentComponent,
        parentSuspense
      ));
    
      // inject renderer internals for keepAlive
      if (isKeepAlive(initialVNode)) {
        (instance.ctx as KeepAliveContext).renderer = internals;
      }
    
      // 2. Install components (that is, component initialization)
      setupComponent(instance);
    
      // setup() is async. This component relies on async logic to be resolved
      // before proceeding
      if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
        parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect);
    
        // Give it a placeholder if this is not hydration
        // TODO handle self-defined fallback
        if(! initialVNode.el) {const placeholder = (instance.subTree = createVNode(Comment));
          processCommentNode(null, placeholder, container! , anchor); }return;
      }
    
      // 3. Install side effect: finish rendering and define update function
      setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized);
    };
    Copy the code

    Component initialization includes:

    1. createComponentInstance: createComponent instance
    2. setupComponent: Installs components (component initialization). (similar to theVue2Initializing the executionvm._initMethods)
    3. setupRenderEffect: Installs the effects of the render function, completes the component rendering, and defines the update function for the component. (effectReplaced theVue2theWatcher)

1.2. Component initialization

1.2.1. setupComponent

  • role

    • Installing components (component initialization)
      • mergeOptions,
      • Define instance properties, events, processing slots,
      • throughsetupStatefulComponentMethods to completeData response
  • The core source

    function setupComponent(instance: ComponentInternalInstance, isSSR = false) {
      isInSSRComponentSetup = isSSR;
    
      const { props, children, shapeFlag } = instance.vnode;
      const isStateful = shapeFlag & ShapeFlags.STATEFUL_COMPONENT;
      // Initialize props
      initProps(instance, props, isStateful, isSSR);
      // Initialize the slot
      initSlots(instance, children);
    
      // Data responsive
      const setupResult = isStateful ? setupStatefulComponent(instance, isSSR) : undefined;
    
      isInSSRComponentSetup = false;
      return setupResult;
    }
    Copy the code

    SetupStatefulComponent is responsible for data responsiveness

(1)setupStatefulComponent

  • role

    • Complete data responsiveness
  • The core source

    function setupStatefulComponent(instance: ComponentInternalInstance, isSSR: boolean) {
      // createApp configuration object
      const Component = instance.type as ComponentOptions;
    
      // 0. create render proxy property access cache
      instance.accessCache = Object.create(null);
    
      // 1.render function context
      instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);
    
      // 2. Handle the setup function
      const { setup } = Component;
      if (setup) {
        const setupContext = (instance.setupContext =
          setup.length > 1 ? createSetupContext(instance) : null);
    
        currentInstance = instance;
        pauseTracking();
        const setupResult = callWithErrorHandling(setup, instance, ErrorCodes.SETUP_FUNCTION, [
          __DEV__ ? shallowReadonly(instance.props) : instance.props,
          setupContext,
        ]);
        resetTracking();
        currentInstance = null;
    
        if (isPromise(setupResult)) {
          if (isSSR) {
            ...
          } else if (__FEATURE_SUSPENSE__) {
            // async setup returned Promise.
            // bail here and wait for re-entry.instance.asyncDep = setupResult; }}else {
          FinishComponentSetup is also executedhandleSetupResult(instance, setupResult, isSSR); }}else{ finishComponentSetup(instance, isSSR); }}Copy the code

    Without setup, handleSetupResult is executed, and finishComponentSetup is still called

(2)finishComponentSetup

  • role

    • Make sure thatinstanceThere areRender function
    • Compatible with Vue2 Options API data response function
  • The core source

    function finishComponentSetup(instance: ComponentInternalInstance, isSSR: boolean) {
      const Component = instance.type as ComponentOptions;
    
      // template / render function normalization
      if (__NODE_JS__ && isSSR) {
        ...
      } else if(! instance.render) {// could be set from setup()
        if(compile && Component.template && ! Component.render) { Component.render = compile(Component.template, {isCustomElement: instance.appContext.config.isCustomElement,
            delimiters: Component.delimiters,
          });
        }
    
        instance.render = (Component.render || NOOP) as InternalRenderFunction;
    
        // for runtime-compiled render functions using `with` blocks, the render
        // proxy used needs a different `has` handler which is more performant and
        // also only allows a whitelist of globals to fallthrough.
        if (instance.render._rc) {
          instance.withProxy = new Proxy(instance.ctx, RuntimeCompiledPublicInstanceProxyHandlers); }}// applyOptions is compatible with Vue2 options API
      if (__FEATURE_OPTIONS_API__) {
        currentInstance = instance;
        pauseTracking();
        applyOptions(instance, Component);
        resetTracking();
        currentInstance = null; }}Copy the code

1.2.2. setupRenderEffect

  • role

    • Side effects of installing render functions
    • Complete component rendering
  • The core source

    const setupRenderEffect: SetupRenderEffectFn = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) = > {
      // create reactive effect for rendering
      instance.update = effect(
        function componentEffect() {
          if(! instance.isMounted) {let vnodeHook: VNodeHook | null | undefined;
            const { el, props } = initialVNode;
            const { bm, m, parent } = instance;
    
            // 1. First get the virtual DOM of the current component
            const subTree = (instance.subTree = renderComponentRoot(instance));
    
            if (el && hydrateNode) {
              ...
            } else {
              // Initialize: perform patch recursivelypatch(...) }}else {
            // updateComponent
            patch(...)
          }
        },
        __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions
      );
    };
    Copy the code

    My understanding of side effects: If reactive data is defined, the side effects function associated with it will be re-executed if the data changes

    Instance. isMounted is used to initialize rendering or update components

    So we find that Effect replaces the Watcher of Vue2

1.3. Process sorting

  1. The entry point to Vue3 is the createApp method, which can obtain a renderer object with one ensureRender, call the renderer.createApp method to return the app object, and extend the $mount method

  2. EnsureRender guarantees that the renderer is a singleton and can be created with the call baseCreateRenderer to createRenderer

  3. BaseCreateRenderer is the method that actually creates the Renderer, which includes Render, Hydrate, and createApp, where the createApp method is created by calling createAppAPI

  4. CreateAppAPI createAppAPI is a factory function that returns a real createApp method that creates an instance method of **Vue (app) ** within createApp and returns it

  5. If the developer calls the mount method, the mount method continues, from Render to Patch, and finally to processComponent, where the data-responsive, real-world DOM is mounted, and the initialization phase is over

1.4. Thinking and summarizing

  1. A renderer is an object that has three parts

    • render
    • hydrate
    • createApp
  2. Why are global methods tuned to instances?

    • Avoid contamination between instances
    • tree-shaking
    • semantic
  3. Vue2 changes are compared during initialization

    • Added the concept of renderers where all methods are provided by renderers
    • Vue2 creates applications by creating objects. Vue3 eliminates the concept of objects and instead returns instances using methods that can be chain-called
    • The root component is a custom component
    • A custom component creates a component instance, initializes it, and installs render/update functions

2. Data response and effect

In Vue2, the data response has the following small defects:

  1. Additional ** API (vue.set/vue.delete) ** is required for dynamically added or deleted keys
  2. Array responsiveness requires a separate set of logic
  3. Initialization is deeply recursive and relatively inefficient
  4. Unable to listen for new data structuresMap,Set

Vue3 solves this problem by refactoring the responsive principle, doubling the speed and reducing the memory footprint by half

The reconstruction content is roughly as follows:

  1. Use proxy instead of Object.defineProperty

  2. Data lazy observation

  3. Optimize the original publish/subscribe model by removing Observer, Watcher and Dep and replacing them with reactive, Effect and targetMap

    • track: Used to trace dependencies
    • trigger: Used to trigger dependencies
    • targetMap: equivalent to publishing subscription center toTree structureRelationships between managed objects, keys, and dependencies

2.1. Explore the process from source code

The core methods for defining Vue3 responsive data are Reactive and REF.

Because Vue3 is still compatible with Vue2, the original options API can continue to be used. After debugging, it is found that reactive is implemented in resolveData. In addition, WHEN I debug ref, I find that Reactive is also used, so it can be considered that Reactive is the data responsive entrance of Vue3.

Before looking at Reactive, let’s look at several types of enumerations for reactive data

2.1.1. ReactiveFlag

export const enum ReactiveFlags {
  SKIP = '__v_skip' / * * /.// Does not need to be proxied
  IS_REACTIVE = '__v_isReactive'.// The flag of a responsive object, similar to Vue2's __ob__
  IS_READONLY = '__v_isReadonly'.// The flag of the read-only object, which cannot be modified
  RAW = '__v_raw' / * * /.// Primitive type
}
Copy the code

Of particular concern are IS_REACTIVE and RAW

2.1.2. reactive

  • role
    • Create reactive objects
  • The core source
    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);
    }
    Copy the code

    In addition toread-onlyObject, and everything else is allowed to executeResponsive processing

2.1.3. createReactiveObject

  • role

    • Create reactive objects without repeating them
  • The core source

    function createReactiveObject(
      target: Target,
      isReadonly: boolean,
      baseHandlers: ProxyHandler<any>,
      collectionHandlers: ProxyHandler<any>
    ) {
      // 1. If it is a read-only property or proxy, return it directly
      if(target[ReactiveFlags.RAW] && ! (isReadonly && target[ReactiveFlags.IS_REACTIVE])) {return target;
      }
      // 2. If the object is already responsive, it is returned directly from the cache
      const proxyMap = isReadonly ? readonlyMap : reactiveMap;
      const existingProxy = proxyMap.get(target);
      if (existingProxy) {
        return existingProxy;
      }
      // 3. Create a responsive object
      const proxy = new Proxy(
        target,
        targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
      );
      // 4. Store in cache
      proxyMap.set(target, proxy);
      return proxy;
    }
    Copy the code

    Weakmap and Weakset do not operate when the type is not Object, Array, Map, Set.

    Handler is used based on Set, Map, and normal objects

    You use baseHandlers, or mutableHandlers, if you are a normal object (containing an array)

2.1.4. mutableHandlers

  • role
    • Define responsive interception methods
      • getterTrigger dependencies collect and define child elements of the responsivity
      • setterTrigger dependent update
  • The core source
const get = function get(target: Target, key: string | symbol, receiver: object) {
  // It is already used for edge cases such as reactive objects, read-only, etc./ / 1. Array
  const targetIsArray = isArray(target);

  if(! isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {return Reflect.get(arrayInstrumentations, key, receiver);
  }

  / / 2. The object
  const res = Reflect.get(target, key, receiver);

  // 3. Rely on tracing
  track(target, TrackOpTypes.GET, key);

  // 4. If it is an object, continue to observe
  if (isObject(res)) {
    // Convert returned value into a proxy as well. we do the isObject check
    // here to avoid invalid value warning. Also need to lazy access readonly
    // and reactive here to avoid circular dependency.
    return reactive(res);
  }

  / / 5. Return
  return res;
};

const set = function set(
  target: object,
  key: string | symbol,
  value: unknown,
  receiver: object
) :boolean {
  const oldValue = (target as any)[key];
  // Edge case judgment.const hadKey =
    isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key);

  const result = Reflect.set(target, key, value, receiver);

  // Do not trigger dependency updates if the target is something in the prototype chain
  if (target === toRaw(receiver)) {
    // Rely on updates
    if(! hadKey) { trigger(target, TriggerOpTypes.ADD, key, value); }else if(hasChanged(value, oldValue)) { trigger(target, TriggerOpTypes.SET, key, value, oldValue); }}return result;
};

function deleteProperty(target: object, key: string | symbol) :boolean {
  const hadKey = hasOwn(target, key);
  const oldValue = (target as any)[key];
  const result = Reflect.deleteProperty(target, key);
  if (result && hadKey) {
    trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue);
  }
  return result;
}

// The catcher for the in operator.
function has(target: object, key: string | symbol) :boolean {
  const result = Reflect.has(target, key);
  if(! isSymbol(key) || ! builtInSymbols.has(key)) { track(target, TrackOpTypes.HAS, key); }return result;
}

/ / Object. The method and Object getOwnPropertyNames. GetOwnPropertySymbols trap method.
function ownKeys(target: object) : (string | number | symbol) []{
  track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY);
  return Reflect.ownKeys(target);
}

export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys,
};
Copy the code
  • getter
    • The triggertrackimplementationDepend on the collection.
    • Keep looking down
  • setter
    • The triggertriggerTo be responsible for theTrigger rely on.

There are three methods to intercept getters in a proxy: GET, HAS, and ownKeys. There are two methods to intercept setters: set and deleteProperty. Unlike Vue2, Vue3’s data listening is executed lazy-only after the getter method is called, effectively reducing the first execution time.

2.1.5. targetMap

  • define

    const targetMap = new WeakMap<any, KeyToDepMap>();
    Copy the code
  • role

    Publish and subscribe center, is a Map structure, with a tree structure to manage each object, the key of the object, the relationship between the corresponding effect of the key is roughly like this

    type targetMap = {
      [key: Object]: {
        [key: string] :Set<ReactiveEffect>;
      };
    };
    Copy the code

2.1.6. activeEffect

  • define

    let activeEffect: ReactiveEffect | undefined;
    Copy the code
  • role

    Is a global variable used to temporarily hold the executing side effect function, essentially a side effect function. A bit like dep.target for Vue2

2.1.7. track

  • role

    • Collect rely on
  • The core source

    export function track(target: object.type: TrackOpTypes, key: unknown) {
      // There are two methods in the source to pause the collection and continue the collection, here is no pause flag
      // The global variable activeEffect is a side effect function set in instance.update or manually by the user
      if(! shouldTrack || activeEffect ===undefined) {
        return;
      }
      // Fetch all the keys of the object from the publish subscribe center
      let depsMap = targetMap.get(target);
      if(! depsMap) { targetMap.set(target, (depsMap =new Map()));
      }
      // Remove all dependencies on the key object
      let dep = depsMap.get(key);
      if(! dep) { depsMap.set(key, (dep =new Set()));
      }
      if (!dep.has(activeEffect)) {
        dep.add(activeEffect);
        activeEffect.deps.push(dep);
      }
    }
    Copy the code

    Collecting dependencies is also a two-way operation,

    TargetMap collects the side effects function, which also needs to reference all the side effects of the current key dependency for future reuse of the dependency. Why is recollecting dependencies explained in more detail below

2.1.8. trigger

  • role

    • Trigger rely on
  • The core source

    export function trigger(
      target: object.type: TriggerOpTypes, key? : unknown, newValue? : unknown, oldValue? : unknown) {
      const depsMap = targetMap.get(target);
      if(! depsMap) {// never been tracked
        return;
      }
    
      const effects = new Set<ReactiveEffect>();
      // Add a side effect function
      const add = (effectsToAdd: Set<ReactiveEffect> | undefined) = > {
        if (effectsToAdd) {
          effectsToAdd.forEach(effect= > {
            if(effect ! == activeEffect || effect.allowRecurse) { effects.add(effect); }}); }};// Select depsMap based on type and key, handle edge cases, and finally ADD, DELETE, and SET to ADD depsMap content to effects.// First render and asynchronous update
      const run = (effect: ReactiveEffect) = > {
        if (effect.options.scheduler) {
          effect.options.scheduler(effect);
        } else{ effect(); }};// Iterate over the side effect function and execute
      effects.forEach(run);
    }
    Copy the code

    There was a lot of code here, but taking out the core logic makes it immediately cleaner.

    The component update function is created with effect passing in a second parameter that contains scheduler, which will be used in the run method here

    The core is to execute the ADD method based on the key and the type of trigger dependency (ADD, DELETE, or SET), and put the side effects of the dependency into effects for batch execution

2.1.9. Side effects

My understanding of side effects is that if reactive data is defined, the side effect function associated with it is re-executed whenever the data changes

During the debug process, we found that watchEffect and Watch were also finally called by the doWatch method, so we can think of Effect as the entry point for creating the side effect function

(1) effectStack

  • define

    const effectStack: ReactiveEffect[] = [];
    Copy the code
  • role

    This is a stack (actually an array) structure that stores multiple side effects functions, ActiveEffects, for handling effects nested scenarios (more on this later).

(2)effect

  • role

    • createSide effect functionIs triggered during executiongettercompleteDepend on the collection
  • The core source

    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;
    }
    
    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;
      // Add many attributes to effect.return effect;
    }
    Copy the code

    The real side effect function is created in the createReactiveEffect method. The side effect function itself is first added to the top of the effectStack, then assigned to activeEffect, followed by fn, which triggers the getter method for reactive data for dependency collection. Add activeEffect to targetMap; When the dependency is triggered when the key changes, the corresponding side effect function is extracted from targetMap and executed, which is a side effect function as a dependency collection and triggering process

    You might also be wondering, activeEffects are used to temporarily store the current side effect function, I get it, but why store it in the effectStack?

    A later look at the Vue3 community revealed that Effect was designed to be nested, and that the stack structure here was designed to handle nested scenarios.

    The stack is characterized by first in, last out, that is, last in side functions are executed first, and then out of the stack, ensuring that the order of execution is from the outside in

    React Hook is not the same as React Hook, it may be the reason WHY I use react hook too much. I can’t accept the nesting for the moment. However, as a React developer, I don’t have much say in the design

(3) Side effects why do you need to re-collect dependencies

If you’re reading the source code for the reactive principle and dependency collection, you might wonder why every time you fire the getter, you go into track, and there’s a dependency collection process.

In fact, this is an edge case where some variables in the side effect function may be read in the condition, so there is a dynamically dependent behavior.

I don’t know much about it, right? It’s a little hard to describe, so let’s just do a little bit of code,

watchEffect(() = > {
  if(state.role === ROOT) { notice(state.user); }});Copy the code

State. user is read in the condition. When the condition is met, the current side effect function is a dependency of state.user. When the condition is not met, state.user needs to clear the dependency. Would it be clearer to describe it this way

2.2. Process Overview

2.2.1. Data responsiveness

  1. Created during initializationResponsive object, to establishgetter,setterInterception,getterResponsible for collecting dependencies,setterResponsible for triggering dependencies
  2. Render timecallThe component leveltheeffectMethod to the componentRender functionAssigned toThe global variableactiveEffectAnd perform,Render functionTrigger the correspondingkeythegetterFunction, doneDepend on the collection
  3. When the user triggers againkeythesetterMethods,targetMapExtract the correspondingDepend on the functionAnd then executetriggermethodsTrigger rely onTo complete the update

2.2.2. effect

So far, we know that ** triggers effect** in the following ways: instance.update, watch, watchEffect, computed

  1. When performing theeffectIs called firstcreateReactiveEffectCreate a realSide effect function
  2. If it iscomputedIs waiting for responsive datagetterThe triggerSide effect functionExecute, or otherwise execute during creation, and eventually firekeythegetterFunction, doneDepend on the collection
  3. Considering theNesting problemsThat will beSide effect functionIn theeffectStackIn the management, each timeperformthenOut of the stackTo ensureSide effect functionOrder of executionFrom outside to inside
  4. Also consider the edge case of dynamic dependencies, so you need to re-collect the dependencies

2.3. Thinking and summarizing

  1. Vue3 has so many advantages of data responsiveness, any disadvantages?

    The new data-responsive solution is efficient and can intercept 13 apis, but the disadvantage is that it is not compatible with older versions of proxy, especially IE, which is still used in 1202. Ha ha ha joke ~

  2. Why Reflect?

    Reflect and Proxy complement each other, as long as there are methods on the Proxy object that Reflect also owns. Using Reflect is actually a security measure to make sure you’re working on the original object

  3. Why cross-reference?

    Similar to Vue2, the DEP and Watcher references each other and are removed when the key is deleted.

    Vue3 also takes this into account by removing the key without removing the side effect function from the key-dependent function in targetMap

  4. Effect nesting problem

    The reason why function components of React cannot use hooks nested is that the design concept of React is different from that of Vue. The Function components of React execute each render as a function from top to bottom and manage the state of each hook through linked lists. This leads to hook chaos if hooks are used in conditions or nesting. However, vue only updates components by triggering dependencies, and there is no rerender, so nesting is reasonable, depending on how developers get used to the idea of switching.

  5. About recollecting dependencies

    React has the side effect of letting developers decide which values to rely on for their function execution. Vue3 does this for us, so developers don’t have to worry about it.

3. Update data asynchronously

Remember that setupRenderEffect was executed during the component but initialization phase, assigning the update function to instance.update via effect,

instance.update = effect(
    function componentEffect() {
        if(! instance.isMounted) {// Initialize: perform patch recursively
            patch(...)
        } else {
            // updateComponent
            patch(...)
        }
    },
    __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions
);
Copy the code

The second argument must have a key attribute {scheduler: queueJob}.

Recalling the effect function, the execution of the side effect function is called through the run method

The core source code is as follows

const run = (effect: ReactiveEffect) = > {
  if (effect.options.scheduler) {
    effect.options.scheduler(effect);
  } else{ effect(); }};Copy the code

You can see that when scheduler is present, asynchronous updates are performed through the scheduler, namely through the queueJob method of instance.update

3.1 explore the principle from the source code

3.1.1. queueJob

  • role
    • Do not repeat forqueueAdd tasks
    • callqueueFlush
  • The core source
    export function queueJob(job: SchedulerJob) {
      if((! queue.length || ! queue.includes(job, isFlushing && job.allowRecurse ? flushIndex +1 : flushIndex)) &&
        job !== currentPreFlushParentJob
      ) {
        queue.push(job);
        queueFlush();
      }
    }
    Copy the code

3.1.2. queueFlush

  • role

    • Execute asynchronous tasks without repeating them
  • The core source

    function queueFlush() {
      if(! isFlushing && ! isFlushPending) { isFlushPending =true; currentFlushPromise = resolvedPromise.then(flushJobs); }}Copy the code

    Those familiar with Vue2’s source code may find Vue3’s asynchronous tasks much simpler,

    The truly asynchronous tasks become completely promises, which are browser-based microtask queues to implement asynchronous tasks

    const resolvedPromise: Promise<any> = Promise.resolve();
    Copy the code

3.1.3. nextTick

  • role
    • Execute custom methods after DOM updates are complete
  • The core source
    export function nextTick(this: ComponentPublicInstance | void, fn? : () = >void) :Promise<void> {
      const p = currentFlushPromise || resolvedPromise;
      return fn ? p.then(this ? fn.bind(this) : fn) : p;
    }
    Copy the code

    currentFlushPromiseisPromise objectThrough thethenThe method will keep going toMicrotask queueAdd methods

3.2. Process sorting

  • When the component is initializedsetupRenderEffectMethod forinstance.updateAssignment update function
  • When the triggersetterThe function will executetriggerTo removeEffect the functionthroughqueueJobperform
  • queueJobAdd tasks toqueueThen run the commandqueueFlushmethods
  • queueFlushIs the realAsynchronous tasks, tasks are added to the microtask queue without being repeated
  • After the current synchronization task is complete, the browser refreshes the microtask queue to complete asynchronous update

3.3. Thinking and summarizing

  1. Vue3’s asynchronous tasks are much cleaner than Vue2’s and no longer compatible with older browsers
  2. The true asynchronous task is the then method of the Promise object

4. patch

Before studying patch, we first need to understand the compiler optimization of Vue3, because it directly changes the structure of VNode and lays a foundation for the extremely high performance of Vue3 patching algorithm.

4.1. Optimizations brought by the compiler

  • 4.1.1. Static node promotion

    Divide nodes into dynamic nodes and static nodes. Static nodes have the same scope as the render function, like this.

    const _hoisted_1 = /*#__PURE__*/ _createTextVNode('A text node');
    const _hoisted_2 = /*#__PURE__*/ _createVNode('div'.null.'good job! ', -1 /* HOISTED */);
    
    return function render(_ctx, _cache) {
      with (_ctx) {
        return _openBlock(), _createBlock('div'.null, [_hoisted_1, _hoisted_2]); }};Copy the code

    Where _hoisted_1 and _HOisted_2 are promoted static nodes that are only created during the first rendering,

    These static nodes will be bypassed in subsequent updates.

  • 4.1.2. Patch marking and dynamic property logging

    When we use a dynamic property in template, it’s recorded, like this,

    <child-comp :title="title" :foo="foo" msg="hello"/>
    Copy the code

    Will be recorded by the render function

    export function render(_ctx, _cache, $props, $setup, $data, $options) {
      const _component_child_comp = _resolveComponent('child-comp');
    
      return (
        _openBlock(),
        _createBlock(
          _component_child_comp,
          {
            title: _ctx.title,
            foo: $setup.foo,
            msg: 'hello',},null.8 /* PROPS */['title'.'foo'])); }Copy the code

    It can be seen that the last two parameters, 8, are a type in PatchFlags, which is essentially a binary number. Combination conditions can be made by bit-by-bit operation. Here, 8 represents the props of the current component that has dynamic changes. The second parameter indicates which props are dynamically changing.

    In subsequent diff props only DiffTitle and foo.

  • 4.1.3. block

    If there are dynamic changes under the current node, it is stored as a block.

    So in Vue3, the render function you should usually see is of this form

    export function render(_ctx, _cache) {
      return _openBlock(), _createBlock('div'.null, [_hoisted_1, _hoisted_2]);
    }
    Copy the code

    _openBlock opens the block, _createBlock creates the block, and all dynamically changing children are stored in dynamicChildren.

    In the future, diff children will only be diff dynamicChildren.

  • 4.1.4. Caching event handlers

    If an inline function is used, it will be cached in _cache and used directly from _cache in the next update. The function will not be created repeatedly to avoid unnecessary rendering due to different references

    Like this,

    <child-comp @click="toggle(index)"/>
    Copy the code

    Will be compiled into

    export function render(_ctx, _cache, $props, $setup, $data, $options) {
      const _component_child_comp = _resolveComponent('child-comp');
    
      return (
        _openBlock(),
        _createBlock(_component_child_comp, {
          onClick: _cache[1] || (_cache[1] = $event => _ctx.toggle(_ctx.index)),
        })
      );
    }
    Copy the code

    For inline functions, if the component is re-rendered, different references to the two functions may lead to repeated updates. This is common in React, which we optimized with useCallback. But Vue3 does that for us.

    Now, what does patching do

4.2. patch

  • role

    • Entry to component rendering and updates
    • By calling theprocessXXXExecute render/update functions of the corresponding type
  • The core source

    const patch: PatchFn = (
        n1,
        n2,
        container,
        anchor = null,
        parentComponent = null,
        parentSuspense = null,
        isSVG = false,
        optimized = false
      ) = > {
        // If patchFlag is included, optimization is enabled
        if (n2.patchFlag === PatchFlags.BAIL) {
          optimized = false
          n2.dynamicChildren = null
        }
    
        const { type, ref, shapeFlag } = n2
        // Determine which patching algorithm to use based on the VNode type
        switch (type) {
          case Text:
            processText(n1, n2, container, anchor)
            break
          case Comment:
            processCommentNode(n1, n2, container, anchor)
            break
          case Static:
            if (n1 == null) {
              mountStaticNode(n2, container, anchor, isSVG)
            } else if (__DEV__) {
              patchStaticNode(n1, n2, container, isSVG)
            }
            break
          case Fragment:
            processFragment(...)
            break
          default:
            if (shapeFlag & ShapeFlags.ELEMENT) {
              processElement(...)
            } else if (shapeFlag & ShapeFlags.COMPONENT) {
              processComponent(...)
            } else if(shapeFlag & ShapeFlags.TELEPORT) { ; (typeas typeof TeleportImpl).process(...)
            } else if(__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { ; (typeas typeof SuspenseImpl).process(...)
            } else if (__DEV__) {
              warn('Invalid VNode type:', type, ` (The ${typeof type}) `)}}// set ref
        if(ref ! =null && parentComponent) {
          setRef(ref, n1 && n1.ref, parentSuspense, n2)
        }
      }
    Copy the code

    Except that Text, Comment, Static, and Fragment are processed by Type, shapeFlag determines which patching algorithm to use in other cases.

    These algorithms are basically similar, and processElement is selected for analysis in this paper

4.3. processElement

  • role

    • Component first render: callmountElement
    • Component update: callpatchElement
  • The core source

    const processElement = (
      n1: VNode | null,
      n2: VNode,
      container: RendererElement,
      anchor: RendererNode | null,
      parentComponent: ComponentInternalInstance | null,
      parentSuspense: SuspenseBoundary | null,
      isSVG: boolean,
      optimized: boolean
    ) = > {
      isSVG = isSVG || (n2.type as string) === 'svg'
      if (n1 == null) {
        mountElement(
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          optimized
        )
      } else {
        patchElement(n1, n2, parentComponent, parentSuspense, isSVG, optimized)
      }
    }
    Copy the code

    N1 represents the old virtual DOM and n2 represents the new virtual DOM. At present, we analyze patching algorithm, so we need to look at patchElement method.

4.4. patchElement

  • role

    • rightElementThe type ofVNodeforpatch
  • The core source

    const patchElement = (
        n1: VNode,
        n2: VNode,
        parentComponent: ComponentInternalInstance | null,
        parentSuspense: SuspenseBoundary | null,
        isSVG: boolean,
        optimized: boolean
      ) = > {
        const el = (n2.el = n1.el!)
        let { patchFlag, dynamicChildren, dirs } = n2
        // #1426 take the old vnode's patch flag into account since user may clone a
        // compiler-generated vnode, which de-opts to FULL_PROPS
        patchFlag |= n1.patchFlag & PatchFlags.FULL_PROPS
        const oldProps = n1.props || EMPTY_OBJ
        const newProps = n2.props || EMPTY_OBJ
        let vnodeHook: VNodeHook | undefined | null
    
        // 1. diff props
        if (patchFlag > 0) {
          if (patchFlag & PatchFlags.FULL_PROPS) {
            // Dynamic key, full diff required.// Finally call patchProps
          } else {
            // class is the case of dynamic attributes.// Finally call hostPatchProp
    
            // style is the case with dynamic attributes.// Finally call hostPatchProp
    
            // Handle dynamic properties in dynamicProps.// Loop to hostPatchProp
          }
    
          / / dynamic text.// Finally call hostSetElementText
        } else if(! optimized && dynamicChildren ==null) {
          // Full diff, i.e., no optimization.// Finally call patchProps
        }
    
        // 2. diff children
        if (dynamicChildren) {
          // Dynamic child nodepatchBlockChildren( n1.dynamicChildren! , dynamicChildren, el, parentComponent, parentSuspense, areChildrenSVG ) }else if(! optimized) {// Full diff, i.e., no optimization
          patchChildren(
            n1,
            n2,
            el,
            null,
            parentComponent,
            parentSuspense,
            areChildrenSVG
          )
        }
      }
    Copy the code

    It’s a little bit too much code, because it also includes the case without optimization, which is basically the same as Vue2,

    With patchFlag, targeted update can be realized through hostPatchProp.

    With the help of dynamicChildren, on-demand diff child nodes can be realized through patchBlockChildren, and full diff can be achieved without patchChildren.

    HostPatchProp is very simple and just updated according to the parameters passed in. We focus on patchBlockChildren and patchChildren

4.5. patchBlockChildren

  • role

    • To deal withblockThe level ofchildren
  • The core source

      const patchBlockChildren: PatchBlockChildrenFn = (oldChildren, newChildren, fallbackContainer, parentComponent, parentSuspense, isSVG) = > {
        for (let i = 0; i < newChildren.length; i++) {
          const oldVNode = oldChildren[i]
          const newVNode = newChildren[i]
          // Determine the container (parent element) for the patch.
          const container =
            // - In the case of a Fragment, we need to provide the actual parent
            // of the Fragment itself so it can move its children.
            oldVNode.type === Fragment ||
            // - In the case of different nodes, there is going to be a replacement
            // which also requires the correct parent container! isSameVNodeType(oldVNode, newVNode) ||// - In the case of a component, it could contain anything.oldVNode.shapeFlag & ShapeFlags.COMPONENT || oldVNode.shapeFlag & ShapeFlags.TELEPORT ? hostParentNode(oldVNode.el!) ! :// In other cases, the parent container is not actually used so we
                // just pass the block element here to avoid a DOM parentNode call.
                fallbackContainer
          patch(
            oldVNode,
            newVNode,
            container,
            null,
            parentComponent,
            parentSuspense,
            isSVG,
            true)}}Copy the code

    Traversal newChildren, namely dynamicChildren, diff the old and new vnodes at the same level through patch, and so on, continuously reduce the level of children, and call patch.

    When invoking patch at a certain level, there will be no optimization option, and the new and old children will be processed eventually

4.6. patchChildren

  • role

    • Patching algorithm for selecting child nodes
  • The core source

    const patchChildren: PatchChildrenFn = (
        n1,
        n2,
        container,
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        optimized = false
      ) = > {
        const c1 = n1 && n1.children
        const prevShapeFlag = n1 ? n1.shapeFlag : 0
        const c2 = n2.children
    
        const { patchFlag, shapeFlag } = n2
        // fast path
        if (patchFlag > 0) {
          if (patchFlag & PatchFlags.KEYED_FRAGMENT) {
            // Children with key
            patchKeyedChildren(...)
            return
          } else if (patchFlag & PatchFlags.UNKEYED_FRAGMENT) {
            // Children without key
            patchUnkeyedChildren(...)
            return}}// children has 3 possibilities: text, array or no children.
        // Children has three possibilities: text, array, or no children. }Copy the code

    Patchflags. KEYED_FRAGMENT and patchflags. UNKEYED_FRAGMENT are the basis of whether children contain keys or not. Select patchKeyedChildren or patchUnkeyedChildren based on whether the key is included.

    Among them, patchKeyedChildren is a treatment method for children with key.

4.7. patchKeyedChildren

  • role

    • diffkeyThe child nodes of the
  • The core source

    const patchKeyedChildren = (
        c1: VNode[],
        c2: VNodeArrayChildren,
        container: RendererElement,
        parentAnchor: RendererNode | null,
        parentComponent: ComponentInternalInstance | null,
        parentSuspense: SuspenseBoundary | null,
        isSVG: boolean,
        optimized: boolean
      ) = > {
        let i = 0
        const l2 = c2.length
        let e1 = c1.length - 1 // prev ending index
        let e2 = l2 - 1 // next ending index
    
        / / 1. The device
        // (a b) c
        // (a b) d e
        while (i <= e1 && i <= e2) {
          const n1 = c1[i]
          const n2 = (c2[i] = optimized
            ? cloneIfMounted(c2[i] as VNode)
            : normalizeVNode(c2[i]))
          if (isSameVNodeType(n1, n2)) {
            patch(...)
          } else {
            break
          }
          i++
        }
    
        / / (2) to the tail
        // a (b c)
        // d e (b c)
        while (i <= e1 && i <= e2) {
          const n1 = c1[e1]
          const n2 = (c2[e2] = optimized
            ? cloneIfMounted(c2[e2] as VNode)
            : normalizeVNode(c2[e2]))
          if (isSameVNodeType(n1, n2)) {
            patch(...)
          } else {
            break
          }
          e1--
          e2--
        }
    
        // 3. If the new node has surplus, the new node is added, and the old node is deleted
        // (a b)
        // (a b) c
        // i = 2, e1 = 1, e2 = 2
        // (a b)
        // c (a b)
        // i = 0, e1 = -1, e2 = 0
        if (i > e1) {
          if (i <= e2) {
            const nextPos = e2 + 1
            const anchor = nextPos < l2 ? (c2[nextPos] as VNode).el : parentAnchor
            while (i <= e2) {
              patch(...)
              i++
            }
          }
        }
    
        // 4. If the old node is available, delete it
        // (a b) c
        // (a b)
        // i = 2, e1 = 2, e2 = 1
        // a (b c)
        // (b c)
        // i = 0, e1 = 0, e2 = -1
        else if (i > e2) {
          while (i <= e1) {
            unmount(c1[i], parentComponent, parentSuspense, true)
            i++
          }
        }
    
        // 5. Diff if there are different nodes in the middle
        // [i ... e1 + 1]: a b [c d e] f g
        // [i ... e2 + 1]: a b [e d c h] f g
        // i = 2, e1 = 4, e2 = 5
        else{... }}Copy the code

    The process is divided into 5 steps in the source code, but can be optimized extraction, divided into three steps

    1. The first step is to find all the same beginnings at once, a process called pinching
    2. Then find all the same endings at once, a process known as tail removal
    3. Break off both endsAfter processing, the process is calledWrap up
      • 3.1. One of the old and new nodes is empty
        • 3.1. If the new array has a surplus, add it.
        • 3.2. Delete the old array if there is any surplus
      • 3.2. Both old and new nodes have surplus
        • Diff two old and new nodes and two remaining children

    An example might be more graphic

    1. Add child nodes

          const oldChildren = [a, b, c, d];
          const newChildren = [a, e, f, b, c, d];
      
          / / 1. The device
          const oldChildren = [b, c, d];
          const newChildren = [e, f, b, c, d];
      
          / / (2) to the tail
          const oldChildren = [];
          const newChildren = [e, f];
      
          // 3. Add a batch of nodes when there is an empty node [e, f].Copy the code
    2. Changes the state of the partial molecular node

      const oldChildren = [a, g, h, b, c, d];
      const newChildren = [a, e, f, b, c, d];
      
      / / 1. The device
      const oldChildren = [g, h, b, c, d];
      const newChildren = [e, f, b, c, d];
      
      / / (2) to the tail
      const oldChildren = [g, h];
      const newChildren = [e, f];
      
      // 3. Diff the two children [g, h] and [e, f]
      const oldChildren = [g, h];
      const newChildren = [e, f];
      Copy the code

5. Think, summarize and supplement

  1. Vue3 makes extensive use of the factory model

    Methods such as createAppAPI and createRenderer return a real function via factory mode. These methods are generally included in the source core package. The purpose here is to make it better cross-platform. Can better cross-platform development, to avoid the development of Weex to directly modify the source code of this situation.

  2. The concept and application of renderer

    The renderer is an object that is a core concept in the Vue3 source code and contains three methods: Render, Hydrate, and createApp.

    If we want to do custom rendering we can do it by creating custom renderers.

  3. Compiler dependence

    • What does the compiler do?

      Parse, transform, and generate convert template to render function

    • At what stage does compilation take place?

      • In a Webpack environment, the compilation is done using vue-Loader in a development environment. Only vue Runtime files are retained in the production environment
      • If it’s with youcompilertheruntimeVersion, the component initializes executionmountMethod is finally calledsetupComponentcompile
    • Optimizations made during compilation

      • Block Tree
      • PatchFlags
      • Static PROPS
      • Cache Event handler
      • prestringing
      • How to implement targeted update
  4. An operation

    Bitwise operations are performed at the base of the number (the 32 digits that represent the number). Since bitwise operations are low-level operations, they tend to be the fastest.

    Combination conditions can be quickly handled, such as scenarios with multiple roles and overlapping permissions in a project that can be solved using bitwise operations.

  5. diff

    Vue3’s real diff algorithm doesn’t change much and can be divided into three steps

    • Pinched head,
    • Go to the tail,
    • Wrap up
      • Batch add/delete
      • diff

    What really makes Vue3 diff fly is that the compiler makes a lot of optimizations.

  6. About data responsiveness

    Not only the object.defineProperty to proxy change, but also the lazy observation of child data.

  7. Composition – API and hook

    Much like React hooks, Vue3 hooks don’t have the mental burden of being different between old and new values, and side functions can be nested, but there is some mental burden of whether or not to deconstruct them.

    Composition-api can reuse logic better than Vue2’s Options API.