Decided to follow Teacher Huang Yi’s VUE2 source course good study vuE2 source code, learning process, try to output their own income, improve learning efficiency, level is limited, wrong words please correct ~

Vue source clone to local, switch to branch 2.6.

Introduction

The piece of componentized rendering, I personally think, is quite complicated and I haven’t sorted it out yet. I decided to look back and see the whole before I hit the hard part.

In fact, vue is also a bit of a configuration king, its configuration is options.

Various operations related to later components revolve around options.

This article focuses on how Vue handles options and, most importantly, how to merge them.

Look at the demo

Try to think for yourself about the order in which the hook functions are printed.

<div id="app"></div>

<script src="/Users/zhm/mygit/vue/dist/vue.js"></script>
<script>
  const log = console.log;

  / / global mixins
  Vue.mixin({
    created() {
      log("mixin created"); }});// Subcomponent instance
  let childCompInstance = null;

  / / child component
  let childComp = {
    name: "MSG".template: "<div>{{msg}}</div>".created() {
      childCompInstance = this;
      log("child created");
    },
    data: () = > ({ msg: "Hello Vue"})};// Root component instance
  let vueInstance = new Vue({
    el: "#app".created() {
      log("parent created");
    },
    render: (h) = > h(childComp),
  });

  log("Options on the Vue constructor", Vue.options);
  log("Options for the Vue instance", vueInstance, vueInstance.$options);
  log(
    "Options for a VueComponent instance",
    childCompInstance,
    childCompInstance.$options,
    childCompInstance.$options.__proto__
  );
</script>
Copy the code

Answer:

mixin created
parent created
mixin created
child created
Copy the code

Options defined in the Vue mixin are merged into the Vue constructor options.

When you create an instance using a constructor, the options on the constructor are merged with the options on the instance.

The component constructor is a subclass of Vue. When merged, the main options are in childCompInstance.$options.

Take a look at the print at the end of the example:

Mixin and options for the Vue constructor

Mixin is a function, and the options passed in are first merged with the options in the Vue constructor.

After vue. mixin is executed, options on the Vue constructor have their corresponding values.

Let’s start with a very simple gesture:

/** vue source */
function Vue(options) {
}
Vue.options = {};

// Merge the two options into one
const mergeOptions = (parentOptions, childOptions) = > {
  let options = {};
  // Lifecycle hooks are merged as arrays
  for (let key in childOptions) {
    if (key === "created") {
      const parentCreated = Array.isArray(parentOptions.created)
        ? parentOptions.created
        : parentOptions.created
        ? [parentOptions.created]
        : [];
      const childCreated = Array.isArray(childOptions.created) ? childOptions.created : [childOptions.created]; options.created = [...parentCreated, ...childCreated]; }}return options;
};
Vue.mixin = function mixin(options) {
  this.options = mergeOptions(Vue.options, options);
  return this
};

/** vue source end */

// Examples of use
Vue.mixin({
  created() {
    console.log("mixin created"); }});// {created:[x]}
console.log(Vue.options);
Copy the code

Basically do the above things, and then look at the real source of Vue

// src/core/global-api/mixin.js
export function initMixin(Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    // Mixin is {created(){}}, this refers to the Vue constructor, because when used it is vue.mixin
    this.options = mergeOptions(this.options, mixin);
    return this;
  };
}
Copy the code
// src/core/util/options.js
export function mergeOptions(
  parent: Object,
  child: Object, vm? : Component) :Object {
  // Parent is the Vue constructor options,child is {}}
  if(process.env.NODE_ENV ! = ="production") {
    checkComponents(child);
  }

  if (typeof child === "function") {
    child = child.options;
  }

  normalizeProps(child, vm);
  normalizeInject(child, vm);
  normalizeDirectives(child);

  // Apply extends and mixins on the child options,
  // but only if it is a raw options object that isn't
  // the result of another mergeOptions call.
  // Only merged options has the _base property.
  if(! child._base) {if (child.extends) {
      parent = mergeOptions(parent, child.extends, 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;
  // Parent is the options on the Vue constructor, and key is each key
  for (key in parent) {
    mergeField(key);
  }
  // Child is {created(){}}, processing the key in the child (parent does not have)
  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;
}
Copy the code

Vue instance

The options on the Vue constructor is merged with the options argument in the instance. Of course the logic of the merge is similar. In this example, it would become created:[fn1,fn2], notice that the constructor is first, and the parameter is second.

// options is similar to {el:'#app',.... }
Vue.prototype._init = function (options) {
  var vm = this;
  {created:[fn1,fn2]}
  vm.$options = mergeOptions(
    Options {created:[fn1]}
    resolveConstructorOptions(vm.constructor),
    // {created:fn2}
    options || {},
    vm
  );
};
Copy the code

Here is a quick look at the static property options on Vue

const ASSET_TYPES = [
  'component'.'directive'.'filter'
]

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 the built-in component to the Vue.options.components, keepAlive Transition
  extend(Vue.options.components, builtInComponents)
  // ...
}
Copy the code

VueComponent instance

The constructor of a component is inherited from Vue through vue.extend.

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

Obviously, the options of the component class are merged with the options of the component object and Vue.

When the component is initialized,

// Options has the following properties
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

Vnode.com ponentOptions. Ctor is pointing to the Vue. Extend the return value of the Sub. If new is executed, this._init(options) is executed again.

export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  // Options for the component class are different from options for the Vue class.
  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 method, equivalent to vm.$options = object.create (sub.options). In this case, vm.$options:

vm.$options = {
  parent: Vue /* Parent Vue instance */.propsData: undefined._componentTag: undefined._parentVnode: VNode /* Parent VNode instance */._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>'}}Copy the code

Go through the debugging

Make a break point at vue.js3:

  • Vue.mixinA breakpoint
  • Vue.prototype._initIn theoptionsMake a break point at
  • function mergeOptionsA breakpoint

First take a look at a mixin:

After Vue. Mixin, Vue’s options merge the parameters of Vue. Mixin to look like this:

{
  components: {}
  created: [ƒ]
  directives: {}
  filters: {}
  _base: ƒ Vue(options)
}


Copy the code

New Vue(options) will merge with Vue’s options, VueComponent’s options will merge with Vue’s options:

The $options of the vue instance is a combination of the options passed by the user and vue’s options.

{
  components: {}
  created: (2) / ƒ, ƒdirectives: {}
  el: "#app"
  filters: {}
  render: (h) = > h(childComp)
  _base: ƒ Vue (options)_isVue: true
  _uid: 0
}
Copy the code

Options for the VueComponent component instance are merged with options for Vue:

Extend, then initInternalComponent, and finally, options for the component instance


  _proto__: {components: {MSG: ƒ}
    created: (2) / ƒ, ƒdata: () = > ({ msg: "Hello Vue" })
    directives: {}
    filters: {}
    name: "MSG"
    template: "<div>{{msg}}</div>"
    _Ctor: {0: ƒ}}parent: Vue {_uid: 0._isVue: true.$options: {... },_renderProxy: Proxy._self: the Vue,... }propsData: undefined
  render: ƒ anonymous( )
  staticRenderFns: []
  _componentTag: undefined
  _parentListeners: undefined
  _parentVnode: VNode {tag: "vue-component-1-MSG".data: {... },children: undefined.text: undefined.elm: div... }Copy the code

conclusion

Options can be merged in two ways during Vue initialization:

  • The external Vue was initialized. ProceduremergeOptionsThe process of merging the finished result remains invm.$optionsIn the.
  • The child component initialization process passesinitInternalComponentWay,mergeOptionsDo it fast

Across the board, the design of some libraries and frameworks is almost always similar,

  • It defines some default configurations,
  • You can also pass in some custom configuration during initialization,
  • thenmergeConfiguration to achieve the purpose of customizing different requirements.

reference

  • Teacher Huang Yi’s VUE2 source decryption course
  • Vue.js technology revealed