Vue2. 0 source code understanding (4) – merge configuration

Time:2022-5-14

Merge configuration

Through the previous source code study, we have learned that there are two main scenarios of new Vue. The first is to actively call new Vue to create an instance externally, and the second is to create a new Vue instance by ourselves when creating sub components within the code. But no matter what kind of new Vue, we need to enter Vue_ Init, execute the mergeoptions function to merge the configuration. In order to be more intuitive, we play with the whole demo debugging.

// src\main.js
let childComp = {
  template:"{{msg}}",
  data(){
    return{
      msg:"childComp"
    }
  },
  created(){
    console.log("childComp created");
  },
  mounted(){
    console.log("childComp mounted");
  }
}

Vue.mixin({
  created(){
    console.log("mixin");
  }
})

let app = new Vue({
  el:"#app",
  render: h => h(childComp)
})

I use vue-cli3. Here is a small detail that needs to be noted. By default, the development environment of vue-cli3 uses the runtime version (node_modules \ Vue \ dist \ Vue. Runtime. ESM. JS). This version does not support compiling templates, and the compiler version needs to be used. This is in Vue config. JS. The configuration code is as follows:

module.exports = {
    runtimeCompiler: true
}

The preparations have been made, so let’s start now_ Init function to see how the merge configuration is described.

// src\core\instance\init.js
Vue.prototype._init = function (options?: Object) {
    ...
    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),  //vue.options
        Options | {}, // options in new Vue
        vm
      )
    }
    ...
}

External call scenario

We can clearly see the combined configuration of the two in the above code. We first entered the positive time non component mode, that is, else. Mergeoptions passed in three input parameters. Let’s see what the resolveconstructoroptions method of the first input parameter did.

// src\core\instance\init.js
export function resolveConstructorOptions (Ctor: Class) {
  let options = Ctor.options
  if (Ctor.super) {
    const superOptions = resolveConstructorOptions(Ctor.super)
    const cachedSuperOptions = Ctor.superOptions
    if (superOptions !== cachedSuperOptions) {
      // super option changed,
      // need to resolve new options.
      Ctor.superOptions = superOptions
      // check if there are any late-modified/attached options (#4976)
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // update base extend options
      if (modifiedOptions) {
        extend(Ctor.extendOptions, modifiedOptions)
      }
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
        options.components[options.name] = Ctor
      }
    }
  }
  return options
}

Input parameter ctor = VM Constructor = Vue. Vue has no parent, so it will not enter the if logic. Therefore, Vue is returned here Options configuration. Vue. Options are defined and configured during initialization.

// src\core\global-api\index.js
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
})
Vue. options._ Base = Vue // createcomponent, mentioned earlier.
Extend (Vue. Options. Components, builtincomponents) // extend some built-in components

Here, asset_ Types is in SRC \ shared \ constants JS defined

// src\shared\constants.js
export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]

Then we’ll go back_ The init function analyzes the mergeoptions function:

// src\core\util\options.js
export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  ...  
  const options = {}
  let key
  、、
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    //When the key is not defined in the parent
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

After a brief part of the code, let’s first focus on the key code of the merger.
In fact, this node traverses the parent (vue.options) and child (options in new Vue), and then calls the MergeField method during the traversal. In this method, we first get a strat function. This function is first found in strats. If it is not found, we use the defaultstrat default function (defaultstrat can consult the source code). We mainly look at strats:

// src\core\util\options.js
const strats = config.optionMergeStrategies

Strats are defined in config, so we can change strats at will. Then in options In JS, strats extends many attributes. Each attribute (key) is a consolidation strategy. Those who are interested can study it one by one. Because our example is the consolidation of life cycle, we first choose the consolidation strategy of life cycle to analyze, and then analyze others.

// src\core\util\options.js
LIFECYCLE_HOOKS.forEach(hook => {
  strats[hook] = mergeHook
})

LIFECYCLE_ Hooks is defined in SRC \ shared \ constants js

// src\shared\constants.js
export const LIFECYCLE_HOOKS = [
  'beforeCreate',
  'created',
  'beforeMount',
  'mounted',
  'beforeUpdate',
  'updated',
  'beforeDestroy',
  'destroyed',
  'activated',
  'deactivated',
  'errorCaptured'
]

Traverse these values and define their merging strategies. In fact, they are all mergehook methods, which are the same merging strategies. Let’s take a look at the mergehook function:

// src\core\util\options.js
function mergeHook (
  parentVal: ?Array,
  childVal: ?Function | ?Array
): ?Array {
  return childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
        ? childVal
        : [childVal]
    : parentVal
}

This multi-layer nested ternary expression looks complex, but it is not difficult. We can understand it in segments:
① : childval has value: enter ②,
Childval has no value: assign parentval;
② : parentval has values: parentval and childval arrays are merged,
Parentval has no value: enter ③;
③ : childval is an array: assign childval,
Childval is not an array: assign [childval];
Finally, we return an array to the mergeoptions function.

Now let’s go back to Vue in the demo Mixin is defined. Its source code actually calls mergeoptions. Let’s look at the source code:

// src\core\global-api\mixin.js
export function initMixin (Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    this.options = mergeOptions(this.options, mixin)
    return this
  }
}

The source code of mixin is very simple. In fact, it calls mergeoptions to Vue Options are merged. There is a small detail to pay attention to, which is Vue in the demo The code sequence of mixin and new Vue must be set to Vue Mixin is defined, otherwise Vue will be created when new Vue is created When options and new Vue options are merged, Vue will be lost Mixin’s, because at that time Vue Mixin does not execute mergeoptions to merge options into Vue Options.

Component scenario

Next, let’s look at another case, component merge configuration. That is, in_ The initinternalcomponent function is run in the inti method. Let’s analyze what it does?

// src\core\instance\init.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
  }
}

The merging of sub components is relatively simple, VM$ Options inherits the subcomponent constructor VM constructor. Options, and then mount some configurations on it. Let’s mainly look at VM constructor. How options came from.

// src\core\global-api\extend.js
Vue.extend = function (extendOptions: Object): Function {
    const Super = this
    ...
    const Sub = function VueComponent (options) {
      this._init(options)
    }
    //The constructor points to itself
    Sub.prototype.constructor = Sub
    //Merge configuration
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    ...
}

Actually, Vue During extend, the constructor of sub components is defined, and Vue Options (super. Options) and sub component options (extendoptions) are merged.
So VM. In initinternalcomponent$ Options is actually a Vue Options and options of subcomponents are combined into a set of configurations.

summary

So far, Vue’s options merging has come to an end. We need to know that it has two scenarios, external call scenario and component scenario.
In fact, the design of some libraries and frameworks is similar. They all have their own default configuration. At the same time, it allows developers to customize the configuration during initialization, and then combine the two configurations to meet the needs of various scenarios. This design idea is also an essential thinking mode when we write components or architecture.

Recommended Today

Introduction and classification of automated testing. It’s enough to read this article

What is automated testing? Automated testing is an important branch and component of software testing activities, that is, using tools or scripts to achieve the purpose of testing. Software testing activities with no or little manual participation are called automated testing What are the advantages of automated testing? It is convenient for regression testing. When […]