Componentize – Merge configuration

We talked in previous posts, initialize the Vue instance there are two ways, one is manual call new Vue (options) method, the second is call new verse on vnode.com ponentOptions. Ctor initialization (options). Either way will be executed to _init (options) method, and then execute the merge options logic, relevant code defined in SRC/core/instance/init. In js:

Vue.prototype._init = function (options? :Object) {
  // merge options
  if (options && options._isComponent) {
    // optimize internal component instantiation
    // since dynamic options merging is pretty slow, and none of the
    // internal component options needs special treatment.
    initInternalComponent(vm, options);
  } else {
    vm.$options = mergeOptions(
      resolveConstructorOptions(vm.constructor),
      options || {},
      vm
    );
  }
  // ...
};
Copy the code

Let’s take a look at the different merging processes for the two scenarios through an 🌰 :

import Vue from "vue";

let childComp = {
  template: "<div>{{msg}}</div>".created() {
    console.log("child created");
  },
  mounted() {
    console.log("child mounted");
  },
  data() {
    return {
      msg: "Hello Vue"}; }}; Vue.mixin({created() {
    console.log("parent created"); }});new Vue({
  el: "#app".render: (h) = > h(childComp),
});
Copy the code

External invocation scenario

One: Execute first

Vue.mixin({
  created() {
    console.log("parent created"); }});Copy the code

The vue. mixin method assigns a value to the Vue constructor in the initGlobalAPI method:

import { mergeOptions } from ".. /util/index";
export function initMixin(Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    this.options = mergeOptions(this.options, mixin);
    return this;
  };
}
Copy the code

You can see that vue. mixin receives an Object and then calls mergeOptions to merge the configuration.

Call this._init(options) and then execute _init(options) to merge options:

vm.$options = mergeOptions(
  resolveConstructorOptions(vm.constructor),
  options || {},
  vm
);
Copy the code

The mergeOptions method is essentially passing in two objects and merging them. ResolveConstructorOptions (vm) constructor) temporarily don’t consider, here it is returned to the vm. The constructor. The options, rather then the Vue. The options, so what is the value, It is defined when the initGlobalAPI method is executed as follows:

export function initGlobalAPI(Vue: GlobalAPI) {
  // ...
  Vue.options = Object.create(null);
  ASSET_TYPES.forEach((type) = > {
    Vue.options[type + "s"] = Object.create(null);
  });

  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue;

  extend(Vue.options.components, builtInComponents);
  // ...
}
Copy the code

The result of this code is as follows:

Vue.options = {
  components: {
    KeepAlive: {
      name: "keep-alive".// ...}},created: [].directives: {},
  filters: {},
  _base: Vue,
};
Copy the code

Extend Vue built-in components (such as KeepAlive) to Vue.options.components via extend(Vue.options.components, builtInComponents). This is why we use KeepAlive instead of registering later, more on component registration later.

Back to the mergeOptions function, which is defined in SRC /core/util/options.js:

export function mergeOptions(
  parent: Object,
  child: Object, vm? : Component) :Object {
  if(process.env.NODE_ENV ! = ="production") {
    checkComponents(child);
  }
  if (typeof child === "function") {
    child = child.options;
  }
  // Standardize props, inject, directive options
  normalizeProps(child, vm);
  normalizeInject(child, vm);
  normalizeDirectives(child);
  const extendsFrom = child.extends;
  if (extendsFrom) {
    parent = mergeOptions(parent, extendsFrom, vm);
  }
  if (child.mixins) {
    for (let i = 0, l = child.mixins.length; i < l; i++) { parent = mergeOptions(parent, child.mixins[i], vm); }}const options = {};
  let key;
  for (key in parent) {
    mergeField(key);
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key);
    }
  }
  // Merge fields, the child option overrides the child option
  function mergeField(key) {
    // Strats or defaultStrat is a merge strategy, that is, parent or child
    const strat = strats[key] || defaultStrat;
    // The value of the child option is preferred
    options[key] = strat(parent[key], child[key], vm, key);
  }
  return options;
}
Copy the code

MergeOptions does a few things:

  1. The recursionextendsmixinsIncorporated into theparent
  2. traverseparent, the callmergeField
  3. traversechildIf thekeyparentDoes not exist onmergeField

Executing mergeField calls different merge strategies for different keys. For example, for life cycles, this is done:

function mergeHook(
  parentVal: ?Array<Function>,
  childVal: ?Function|?Array<Function>
): ?Array<Function> {
  return childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
      ? childVal
      : [childVal]
    : parentVal;
}

[
  "beforeCreate"."created"."beforeMount"."mounted"."beforeUpdate"."updated"."beforeDestroy"."destroyed"."activated"."deactivated"."errorCaptured".// New hooks in 2.5.0
].forEach((hook) = > {
  strats[hook] = mergeHook;
});
Copy the code

This defines all lifecycle hook functions in Vue, and mergeHook execution results in merging child and parent functions such as created(){} into an array and returning it. Other policy definitions can be found in SRC /core/util/options.js, but we won’t go through them here.

Therefore, after we finally perform merge configuration:

vm.$options = mergeOptions(
  resolveConstructorOptions(vm.constructor),
  options || {},
  vm
);
Copy the code

The vm.$options value should be:

vm.$options = {
  components: {}
  created: [
    function created() {
      console.log('parent created')}]directives: {}
  el: "#app"
  filters: {}
  render: function render(h) {}
  _base: function Vue(options) {}}Copy the code

Component invocation scenario

We learned earlier that the component’s constructor is inherited from Vue via vue. extend. The code is defined in SRC /core/global-api/extend.js:

Vue.extend = function (extendOptions: Object) :Function {
  // ...
  Sub.options = mergeOptions(Super.options, extendOptions);

  // ...
  // keep a reference to the super options at extension time.
  // later at instantiation we can check if Super's options have
  // been updated.
  Sub.superOptions = Super.options;
  Sub.extendOptions = extendOptions;
  Sub.sealedOptions = extend({}, Sub.options);

  // ...
  return Sub;
};
Copy the code

Let childComp = {//… }, it will merge with vue. options into sub. options. Next we’ll look at the component initialization process, defined in SRC /core/vdom/create-component.js:

export function createComponentInstanceForVnode(
  vnode: any, // we know it's MountedComponentVNode but flow doesn't
  parent: any // activeInstance in lifecycle state
) :Component {
  const options: InternalComponentOptions = {
    _isComponent: true._parentVnode: vnode,
    parent,
  };
  // ...
  return new vnode.componentOptions.Ctor(options);
}
Copy the code

Here vnode.com ponentOptions Ctor = Sub, so the next step is to execute call _init (options) method. Since _isComponent = true, merge configuration goes to initInternalComponent(VM, options), InitInternalComponent defined in SRC/core/instance/init. In js:

export function initInternalComponent(vm: Component, options: InternalComponentOptions) {
  const opts = (vm.$options = Object.create(vm.constructor.options));
  // doing this because it's faster than dynamic enumeration.
  const parentVnode = options._parentVnode;
  opts.parent = options.parent;
  opts._parentVnode = parentVnode;

  const vnodeComponentOptions = parentVnode.componentOptions;
  opts.propsData = vnodeComponentOptions.propsData;
  opts._parentListeners = vnodeComponentOptions.listeners;
  opts._renderChildren = vnodeComponentOptions.children;
  opts._componentTag = vnodeComponentOptions.tag;

  if(options.render) { opts.render = options.render; opts.staticRenderFns = options.staticRenderFns; }}Copy the code

InitInternalComponent does a few things:

  1. performconst opts = vm.$options = Object.create(vm.constructor.options), that is,vm.$options = Object.create(Sub.options)
  2. The child component that passes the instantiated child componentFather VNodeThe instanceparentVnode, subcomponentFather Vue instance parentSave tovm.$options
  3. Retain theparentVnodeThe following information in the configurationpropsDataAnd so on

There is no logic like mergeOptions for recursion, merge strategy, etc.

So after executing initInternalComponent, the vm.$options value should be:

vm.$options = {
  parent: Vue, // Parent Vue instance
  propsData: undefined._componentTag: undefined._parentListeners: undefined._renderChildren: undefined._parentVnode: VNode, // Parent VNode instance
  __proto__: {
    components: {},
    created: [
      function created() {
        console.log("parent created");
      },
      function created() {
        console.log("child created"); },].data() {
      return {
        msg: "Hello Vue"}; },directives: {},
    filters: {},
    mounted: [
      function mounted() {
        console.log("child mounted"); },].template: "<div>{{msg}}</div>"._Ctor: {},
    _base: function Vue(options) {
      / /...,}}};Copy the code

conclusion

In this article, we summarize the two merge configuration procedures for initializing Vue. We only need to know that there are two merge configuration procedures for options. The component initialization process by calling initInternalComponent is faster than the external Vue initialization process.

The design of some libraries and frameworks is almost similar. They define some default configurations, and at the same time, they can pass in some defined configurations during the initialization phase, and then merge the default configurations to achieve the purpose of customizing different requirements. However, in the case of Vue, there is some fine control over the merge process, although we are not as complex as Vue when developing our OWN JSSDK, but this design idea is worth learning. – Vue.js technology revealed

Source code analysis GitHub address

Reference: Vue. Js technology revealed