This is the 16th day of my participation in the August Text Challenge.More challenges in August


One, foreword

In the first part, “Component part – Brief Description of Component Mounting Process” was introduced.

At this point, [Vue2. X source code learning] column core several topics are initially completed, including:

  1. Principles of Responsive Data (Part 1 ~ part 10)
  2. Template Compilation Principles (Part 11 ~ part 20)
  3. Dependency collection, asynchronous updates, lifecycle (XXI-XVII)
  4. Principles of DiFF Algorithm (28 ~ 33)
  5. Component Part (34 ~ 42)

A brief summary of the problem and the follow-up improvement of the column:

  • Part of the principle of responsive data: the writing is a bit chaotic, and the order needs to be adjusted again. Fortunately, things are clear;
  • Part of the principle of template compilation: part of the content still needs to be refined, to speak clearly about the process, let a person understand;
  • Diff algorithm part: also need to add some necessary graphics and animation, this part is not difficult, but not easy to do;
  • Component part: I was busy in the last half month, and the quality of this part was not high. In the second round, this part was optimized first.

It is planned to enter the second round of optimization after the completion of the activity in August.

Objective: To repair several articles with large problems, and to sort out and plan the content of the third round of optimization in the process;

Back; This part of the component part of several articles to do a periodic summary “component part – component related process summary”;


Second, the main process division

  • The realization of Vue.com ponent
  • The realization of Vue. The extend
  • Implementation of component merge
  • Implementation of component compilation
  • Create a virtual node for the component
  • Implementation of the component lifecycle
  • Create the real node of the component
  • Implementation of component mount

Three, the realization of each process brief

1. Implementation of Vue.com Ponent

  • In the Vue initialization process, the global API is processed in a centralized manner and Vue.com Ponent API is created.
  • Save the Vue to a global objectVue.optionsSo that the component can pass through later in the processvm.$options._baseAccess to the Vue; Note: Since there is no extend method on subclass components, Vue is required to treat component definition objects as component constructors;
  • In Vue.com Ponent, if a component is defined as an object, it is treated as a component constructor using vue.extend.
  • extensionVue.optionsObject that maintains the global component definition toVue.options.componentsOn;
// src/global-api/index.js
export function initGlobalAPI(Vue) {
  Vue.options = {};
  $options._base = Vue;
  Vue.options._base = Vue;
  Vue.options.components = {};
  Vue.extend = function (definition) {}
  Vue.component = function (id, definition) {
    let name = definition.name || id;
    definition.name = name;
    // Process component definitions and generate component constructors
    if(isObject(definition)){
      definition = Vue.extend(definition)
    }
    // Maintain the mapping between components and constructorsVue.options.components[name] = definition; }}Copy the code

Vue.options.com Ponents

  • Using global objectsvm.constructor.optionsComplete the merge of global components and local components;
  • Query the constructor of the component based on the label name of the virtual node of the component to complete the instantiation of the component.

2, Vue. Extend implementation

  • Vue.extendUsing the base Vue constructor, create a subclass;
  • Vue.extendInternally, a component subclass Sub is generated from the Vue prototype based on the component definition.
  • Fixed constructor pointing problem: due toObject.createGenerates a new instance as a prototype for the subclass, causing constructor to point to an error that should point to the current subclass Sub;
  • Return the component’s constructor Sub,Vue.componentThe component constructors are mapped globally
// src/global-api/index.js
export function initGlobalAPI(Vue) {
  Vue.extend = function (definition) {
    const Super = this;
    const Sub = function (options) {
      this._init(options);
    }
    // Subclasses inherit from their parent class
    Sub.prototype = Object.create(Super.prototype);
    // Fix constructor pointing to Sub
    Sub.prototype.constructor = Sub;
    returnSub; }}Copy the code

3. Implementation of component merging

  • At this point,vm.constructor.optionsContains theVue.options.componentsGlobal components in;

When new Vue is executed

  • performnew Vue, the component is initialized and the _init method is entered.
  • in_initMethod, throughmergeOptionsMethod: Pass new Vue to the local component definitionoptionsMerge operations with global component definitions;
  • inmergeOptionsIn the method, the preset component merge strategy function is obtained by the policy mode.
  • Component merge strategy: Create new objects that inherit from global component definitions and add local component definitions to new objects; In this case, the local component definition will be searched in the new object first. If it is not found, the global component definition will be searched through the inheritance relationship on the chain.
// src/init.js#initMixin
Vue.prototype._init = function (options) {
    const vm = this;
    // Component merge
    vm.$options = mergeOptions(vm.constructor.options, options);
    initState(vm);
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
}
Copy the code
// src/utils.js
let strats = {};  // Hold the policy function
// Set the merge policy for components
strats.component = function (parentVal, childVal) {
  // Put the global component definition on the chain
  let res = Object.create(parentVal); 
  // Place the local component definition on the object
  if(childVal){
    for (let key in childVal) {
     res[key] = childVal[key];
    }
    // The local component definition will be searched first. If not, the global component definition will be searched through the inheritance relationship on the chain.
    returnres; }}// Merge options according to merge strategy
export function mergeOptions(parentVal, childVal) {
  let options = {};
  for(let key in parentVal){
    mergeFiled(key);
  }
  for(let key in childVal){
    if(!parentVal.hasOwnProperty(key)){
      mergeFiled(key);
    }
  }

  function mergeFiled(key) {
    // Policy mode: get the merge policy of the current key
    let strat = strats[key];
    if(strat){  
      options[key] = strat(parentVal[key], childVal[key]);
    }else{  // Default merge strategy: new value overwrites old valueoptions[key] = childVal[key] || parentVal[key]; }}return options;
}
Copy the code

Note:

  • invm.constructor.optionsThe global component in Vue. Extend, which may have been treated as a function (a component constructor);
  • inoptionsThe local component in Vue. Extend is not processed and is still an object;

4. Implementation of component compilation

  • Similar to the template compilation process: Component template -> AST syntax tree -> Render function

5. Create a virtual node for the component

  • In the Render function, the createElement method generates the virtual node of the component;
  • increateElementIn the method, label screening is carried out. If there is no non-common label, it is regarded as a component. Get the component definition (possibly a constructor) and create the component virtual node componentVnode using the createComponent method.
  • increateComponentIf the obtained component defines Ctor as an object, you must pass theVue.extendHandle as a component’s constructor;
  • The get is saved globally beforehandvm.$options._baseIn Vue, implement vue.extend to generate the component constructor;
  • The virtual node componentVnode is generated by vNode method, and the component information is encapsulated into componentOptions object. Complete componentOptions include: Ctor, propsData, Listeners, Tag, Children;
// src/vdom/index.js
export function createElement(vm, tag, data={}, ... children) {
  // Handle component types
  if(! isReservedTag(tag)) {let Ctor = vm.$options.components[tag];
    // Create a virtual node for the component
    return createComponent(vm, tag, data, children, data.key, Ctor);
  }
  // Create a virtual node for the element
  return vnode(vm, tag, data, children, data.key, Ctor);
}

// Create a component virtual node componentVnode
function createComponent(vm, tag, data, children, key, Ctor) {
  if(isObject(Ctor)){
    // Create the component's constructor with vue.extend
    Ctor = vm.$options._base.extend(Ctor)
  }
  let componentVnode = vnode(vm, tag, data, undefined, key, undefined, {Ctor, children, tag});
  return componentVnode;
}
Copy the code

Note that all components are eventually treated as component constructors by vue.extend:

  • Global components: may have been processed by vue.extend inside Vue.com Ponent;
  • Local component: when createComponent creates a component virtual node, it is processed by vue.extend;

Implementation of the component lifecycle

  • When createComponent creates a component virtual node, it adds a lifecycle hook function to the component by extending the data attribute.
  • Component initialization, through the implementation of init hook function, component instantiation and complete page mount;
// src/vdom/index.js
function createComponent(vm, tag, data, children, key, Ctor) {
  if(isObject(Ctor)){
    Ctor = vm.$options._base.extend(Ctor)
  }
  // Extend the component lifecycle
  data.hook = {
    init(){
      console.log("Hook-init: Perform component instantiation and complete mount");
      // Note: the VM here is not a component instance. You need to access the current component instance
      let child = vnode.componentInstance = new Ctor({});
      child.$mount();
    },
    prepatch(){},
    postpatch(){}}let componentVnode = vnode(vm, tag, data, undefined, key, undefined, {Ctor, children, tag});
  return componentVnode;
}
Copy the code

You can save the component instance to vnode.componentInstance on the virtual node to obtain the real node and mount the component.


Create a real node for the component

  • CreateElm creates a real node from the component virtual node by executing the createComponent method.

Note: After createComponent is executed, vnode.componentInstance is set to the component instance. Vnode.componentinstance. $el is the real node of the component

// Create real nodes based on virtual nodes (recursive)
export function createElm(vnode) {
  let { tag, data, children, text, vm } = vnode;
  if (typeof tag === 'string') {
    if(createComponent(vnode)){// Component processing: Create a real node based on the virtual node of the component
      return vnode.componentInstance.$el;
    }
    vnode.el = document.createElement(tag) 
    updateProperties(vnode, data)
    children.forEach(child= > {
      vnode.el.appendChild(createElm(child))
    });
  } else {
    vnode.el = document.createTextNode(text)
  }
  return vnode.el;
}
Copy the code
  • In the createComponent method, a hook is a component if it exists. The component initialization is performed through the component init hook function.
// Create a real node based on the virtual node of the component
function createComponent(vnode) {
  let i = vnode.data;
  // Make sure there is a hook; Get init method;
  if((i = i.hook)&&(i = i.init)){
    i(vnode); // Use the init method to process vNodes}}Copy the code
  • $options. El is null and the component will not be automatically mounted.
  Vue.prototype._init = function (options) {
    const vm = this;
    vm.$options = mergeOptions(vm.constructor.options, options);
    initState(vm);
    // Vm.$mount will not be executed because el does not exist
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
Copy the code
  • throughchild.$mount()If $mount el is null, the component will not be mounted.
  Vue.prototype.$mount = function (el) {
    const vm = this;
    const opts = vm.$options;
    el = document.querySelector(el);
    vm.$el = el;
    if(! opts.render) {let template = opts.template;
      if(! template) { template = el.outerHTML; }let render = compileToFunction(template);
      opts.render = render;
    }
    mountComponent(vm);
  }
Copy the code
  • After generating the render function of the component, execute mountComponent to mount the component.
// src/lifeCycle.js
export function mountComponent(vm) {
  let updateComponent = () = >{
    vm._update(vm._render());  
  }
  callHook(vm, 'beforeCreate');
  new Watcher(vm, updateComponent, () = >{
    callHook(vm, 'created');
  },true)
   callHook(vm, 'mounted');
}
Copy the code
  • Create component virtual nodes with _render in updateComponent:
  Vue.prototype._render = function () {
    const vm = this;
    let { render } = vm.$options;
    let vnode = render.call(vm);
    return vnode
  }
Copy the code
  • After vm.render completes execution, proceed with the _update method
// src/lifeCycle.js
export function lifeCycleMixin(Vue){
  Vue.prototype._update = function (vnode) {
    const vm = this;
    let preVnode = vm.preVnode;
    vm.preVnode = vnode;
    if(! preVnode){/ / at the beginning of rendering
      // Pass the current real element vm.$el, the virtual node vnode, and return the real node
      vm.$el = patch(vm.$el, vnode);
    }else{// Update render: Diff comparison between old and new virtual nodesvm.$el = patch(preVnode, vnode); }}}Copy the code
  • PreVnode is null, oldVnode is null in patch method (el of component is null), use component’s virtual node, create component’s real node and return:
export function patch(oldVnode, vnode) {
  if(! oldVnode){// Component mounting process
    return createElm(vnode);  // Use component virtual nodes directly to create real nodes
  }
Copy the code
  • The vm.$el returned is the real node of the component;

8. Implementation of component mounting

  • In the createElm method, a real node is generated recursively and inserted into the corresponding parent node.
  • CreateElm is depth-first traversal, and eventually mounts the complete div to the page;
// Create real nodes based on virtual nodes (recursive)
export function createElm(vnode) {
  let { tag, data, children, text, vm } = vnode;
  if (typeof tag === 'string') {
    if(createComponent(vnode)){// Component processing: Create a real node based on the virtual node of the component
      return vnode.componentInstance.$el;
    }
    vnode.el = document.createElement(tag) 
    updateProperties(vnode, data)
    // Insert the real node into the corresponding parent node
    children.forEach(child= > {
      vnode.el.appendChild(createElm(child))
    });
  } else {
    vnode.el = document.createTextNode(text)
  }
  return vnode.el;
}
Copy the code

Remark:

  • _update executes the beforeCreate hook, generates the component’s independent rendering watcher, and executes the Mounted hook to mount the component
// src/lifeCycle.js
export function mountComponent(vm) {
  let updateComponent = () = >{
    vm._update(vm._render());  
  }
  callHook(vm, 'beforeCreate');
  new Watcher(vm, updateComponent, () = >{
    callHook(vm, 'created');
  },true)
   callHook(vm, 'mounted');
}
Copy the code

Four, the end

In this paper, the component related process and implementation are summarized briefly;

[Vue2. X source learning] the end of the first stage, the follow-up will continue to optimize;