Vue source code learning-componentization (3) merge configuration

Vue source code learning-componentization (3) merge configuration

学习内容和文章内容来自 黄轶老师
黄轶老师的慕课网视频教程地址:"Vue.js2.0 Source Code Revealed" ,
黄轶老师拉钩教育教程地址:"Vue.js 3.0 Core Source Code Analysis"
这里分析的源码是Runtime + Compiler 的 Vue.js
调试代码在:node_modules\vue\dist\vue.esm.js 里添加
vue版本:Vue.js 2.5.17-beta

你越是认真生活,你的生活就会越美好——Frank Lloyd Wright
"The Fruit of Life" Classic Quotations

Click to go back to the complete list of Vue source code learning

Merge configuration

Through source code analysis section before we know, new Vuethe process typically there are two scenarios, one is outside our code automatically call new Vue(options)the subject instantiate a Vue; the other components of our process on an internal analysis by new Vue(options)example化子components.

In either scenario, the instance will execute _init(options)the method, it will first perform a merge optionslogic-related code src/core/instance/init.jsin:

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
    )
  }
  // ...
}

We can see different scenarios for optionsconsolidation logic is not the same, and the incoming optionsvalues also have a very big difference, then I will introduce separate options merger process two kinds of scenes.

To be more intuitive, we can give a simple example:

// src/main.js
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')
  }
})

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

Insert picture description here

Insert picture description here

External call scene

When the execution new Vuetime, the execution this._init(options)time will merge logic to perform the following options:

vm.$options = mergeOptions(
  resolveConstructorOptions(vm.constructor),
  options || {
    
    },
  vm
)

Insert picture description here

Here by calling mergeOptionsto merge method, which is actually the resolveConstructorOptions(vm.constructor)return value and optionsdo merge, resolveConstructorOptionsthe realization is not considered, in our scenario, it is simple returns vm.constructor.options, the equivalent Vue.options, then the value of what is it, in fact, in initGlobalAPI(Vue)the when defining this value, the code src/core/global-api/index.jsin:

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)
  // ...
}

First, by Vue.options = Object.create(null)creating an empty object, and then traverse ASSET_TYPES, ASSET_TYPESthe definition src/shared/constants.jsof:

export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]

Therefore traversal above ASSET_TYPEScode corresponds to:

Vue.options.components = {
    
    }
Vue.options.directives = {
    
    }
Vue.options.filters = {
    
    }

Insert picture description here

Then it was executed Vue.options._base = Vue, and its function was introduced when we instantiated the sub-components in the previous section.

Finally, by extend(Vue.options.components, builtInComponents)extending the number of built-in components to Vue.options.componentson, Vue There are built-in components <keep-alive>, <transition>and <transition-group>components, which is why we use other components <keep-alive>reason components do not require registration, this child follow us <keep-alive>assembly time will tell in detail.

So back to mergeOptionsthis function, which is defined in src/core/util/options.jsthe:

/**
 * Merge two option objects into a new one.
 * Core utility used in both instantiation and inheritance.
 */
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
  }

  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)
    }
  }
  function mergeField (key) {
    
    
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

mergeOptionsThe main function is to parentand childthese two objects, according to some consolidation strategy, combined into a new object and return. More central steps,
before the recursive extendsand mixinsmerged into parentthe,
then traverse the parentcall mergeField,
and then iterate child,
if keynot parenton their own property, then call mergeField.
Insert picture description here

Here is an interesting mergeFieldfunction, which is different keywith different consolidation strategy. For example, for the life cycle function, its merge strategy is like this:

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

LIFECYCLE_HOOKS.forEach(hook => {
    
    
  strats[hook] = mergeHook
})

Insert picture description here

Which is LIFECYCLE_HOOKSdefined in src/shared/constants.jsthe:

export const LIFECYCLE_HOOKS = [
  'beforeCreate',
  'created',
  'beforeMount',
  'mounted',
  'beforeUpdate',
  'updated',
  'beforeDestroy',
  'destroyed',
  'activated',
  'deactivated',
  'errorCaptured'
]

This defines Vue.js all hook function name, so the hook function, their strategies are merging mergeHookfunction.
Implementation of this function is also very interesting, with a multi-layer three yuan operator, the logic is
, if not present childVal, returns parentVal;
otherwise, then determines whether there is parentVal, if there is put childValadded to parentValreturn the new array; otherwise childValarray.

So back mergeOptionsfunctions, once parentand childare defined the same hook function, then they will merge two hook function into an array.

Definition of the consolidation strategy are other attributes can src/core/util/options.jssee the file, here is not introduced one by one, interested students can see themselves.

By performing mergeFieldthe function, the merge results are stored into optionsthe object, it eventually returns.

Therefore, in our current case, after performing the following merge:

vm.$options = mergeOptions(
  resolveConstructorOptions(vm.constructor),
  options || {
    
    },
  vm
)

vm.$options The value of is almost as follows:

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

Insert picture description here

Component scene

Since the constructor component by Vue.extendinherited from Vuethe first look at this process, codes are defined in src/core/global-api/extend.jsthe.

/**
 * Class inheritance
 */
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
}

We only retain key logic, here extendOptionsis the corresponding previously defined component objects, and it will Vue.optionsmerge into the Sub.opitonsmiddle.

Next we recall the initialization process once assembly is defined in the code src/core/vdom/create-component.jsin:

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)
}

Insert picture description here
Here vnode.componentOptions.Ctoris the point Vue.extendof the return value Sub, so execution new vnode.componentOptions.Ctor(options)is then performed this._init(options), as options._isComponentis true, then the merger optionsprocess went initInternalComponent(vm, options)logic. First look at its code to achieve, in the src/core/instance/init.jsmiddle:
Insert picture description here

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
  }
}

initInternalComponentThe method is performed first const opts = vm.$options = Object.create(vm.constructor.options), here vm.constructoris the constructor subcomponent Subcorresponds vm.$options = Object.create(Sub.options).

Then again passed instantiating subunit component subassembly parent instance VNode parentVnodeparent Vue example, the subassembly parentis saved to vm.$optionsthe, also retains the parentVnodeconfiguration as propsDataother attributes.

In this way, it initInternalComponentjust 做了简单一层对象赋值doesn't involve complicated logic such as recursion and merge strategy.

Therefore, in our current case, after performing the following merge:

initInternalComponent(vm, options)

vm.$options The value of is almost as follows:

vm.$options = {
    
    
  parent: Vue /*父Vue实例*/,
  propsData: undefined,
  _componentTag: undefined,
  _parentVnode: VNode /*父VNode实例*/,
  _renderChildren:undefined,
  __proto__: {
    
    
    components: {
    
     },
    directives: {
    
     },
    filters: {
    
     },
    _base: function Vue(options) {
    
    
        //...
    },
    _Ctor: {
    
    },
    created: [
      function created() {
    
    
        console.log('parent created')
      }, function created() {
    
    
        console.log('child created')
      }
    ],
    mounted: [
      function mounted() {
    
    
        console.log('child mounted')
      }
    ],
    data() {
    
    
       return {
    
    
         msg: 'Hello Vue'
       }
    },
    template: '<div>{
    
    {msg}}</div>'
  }
}

Insert picture description here

to sum up

So far, Vue initialization phase for optionsthe consolidation process is introduced over, we need to know for optionsthe merger in two ways, sub-component initialization process by initInternalComponentexternal initialization Vue than by way of mergeOptionsprocess is faster, merging process follow certain consolidation strategy, End result of the merger remains in the vm.$optionsmiddle.

Looking at some libraries, design framework almost all similar, 自身定义了一些默认配置,同时又可以在初始化阶段传入一些定义配置and then go to mergethe default configuration to achieve 定制化the purpose of different needs. It's just that in the case of Vue, we will do some fine control of the merge process. Although we are not as complicated as Vue when we develop our own JSSDK, this design idea is worth learning from.

Vue source code learning-componentization (1) createComponent
Vue source code learning-componentization (2) patch
Vue source code learning-componentization (4) life cycle

Click to go back to the complete list of Vue source code learning


谢谢你阅读到了最后~
期待你关注、收藏、评论、点赞~
让我们一起 变得更强

Guess you like

Origin blog.csdn.net/weixin_42752574/article/details/111147188