I. Introduction
In the previous article, I introduced the component part - the combination of components, mainly involving the following points:
- Component initialization;
- where the components are merged;
- strategy for merging components;
- Post-merge testing of components;
This article, the component part - the compilation of components;
Second, the previous review
- Declare global components via the Vue.component global API;
- Vue.component internally uses Vue.extend to generate the component's constructor;
- Maintain the component constructor in the global object Vue.options.components for backup;
- When new Vue is initialized, in the mergeOptions method, use the strategy pattern to find the merge strategy of the component, and complete the merge operation of the global component and the local component;
So far, in vm.$options, the construction of component relationship has been completed;
Component search rules: search for local components first, and continue to search for global components on the chain if they cannot be found;
Review the template parsing process:
- The first step is to generate an AST syntax tree from the html template;
- The second step is to generate a render function according to the AST syntax tree;
- In the third step, in the render function, call _c (that is, the createElement method) to process the label and generate the virtual node vnode of the element label;
Three, the compilation process of components
<div id="app"> <my-button></my-button> </div>
Take the above component as an example, the compilation process of the component is similar to the template:
- The first step is to generate an AST syntax tree according to the component html template;
- The second step is to generate a render function according to the AST syntax tree;
- In the third step, in the render function, call _c to process the component and generate the virtual node componentVnode of the component;
The difference between component and label compilation:
- Labels need to generate virtual nodes for labels;
- The component needs to generate a virtual node of the component;
Therefore, the original createElement method can be extended to support component compilation and generate component virtual nodes;
Fourth, generate component virtual nodes
1. Extend the createElement method
The original createElement method: generate the virtual node vnode of the label element:
// Parameters: _c('label', {property}, ... son) export function createElement(vm, tag, data={}, ...children) { // Returns the virtual node of the element (elements have no text) return vnode(vm, tag, data, children, data.key, undefined); }
Now, due to the addition of components, the tag in the createElement method is not necessarily an element, but may also be a component;
export function createElement(vm, tag, data={}, ...children) { // Processing when adding a tag as a component, creating a virtual node componentVnode of the component // todo ... console.log(tag); return vnode(vm, tag, data, children, data.key, undefined); }
log prints out 2 times:
- First time: my-button (component)
- Second time: div (label)
Therefore, the createElement method needs to be extended: add the processing logic when tag is a component, and create the virtual node componentVnode of the corresponding component;
2. Distinguish components or elements
Judgment basis: Whether the tag belongs to the original/normal tag:
- If the tag belongs to the original tag, it means that the tag is an element, such as: div;
- If the tag does not belong to the original tag, it means that the tag is a component, such as: my-button;
// The processing logic when adding tag as a component creates a virtual node of the component export function createElement(vm, tag, data={}, ...children) { // Judging whether it is a component or an element node: whether it belongs to a normal label if (!isReservedTag(tag)) {// Components: non-ordinary tags are components // todo... } // Create virtual nodes for elements return createComponent(vm, tag, data, children, data.key, Ctor);// Create virtual nodes for components } /** * Create component virtual node componentVnode */ function createComponent(vm, tag, data, children, key, Ctor) { // todo... } // Determining inclusion function makeMap(str) { let tagList = str.split(','); return function (tagName) { return tagList.includes(tagName); } } // original label export const isReservedTag = makeMap( 'template,script,style,element,content,slot,link,meta,svg,view,button,' + 'a,div,img,image,text,span,input,switch,textarea,spinner,select,' + 'slider,slider-neighbor,indicator,canvas,' + 'list,cell,header,loading,loading-indicator,refresh,scrollable,scroller,' + 'video,web,embed,tabbar,tabheader,datepicker,timepicker,marquee,countdown' )
3, createComponent method definition
Get the constructor of the corresponding component and create a component virtual node:
- Get the constructor of the corresponding component through vm.$options.components;
- Create the virtual node of the component through the createComponent method;
// The processing logic when adding tag as a component creates a virtual node of the component export function createElement(vm, tag, data={}, ...children) { if (!isReservedTag(tag)) {// components // Get the constructor of the component: it has been saved to the global vm.$options.components before; let Ctor = vm.$options.components[tag]; // Create virtual nodes for components return createComponent(vm, tag, data, children, data.key, Ctor); } // Create virtual nodes for elements return vnode(vm, tag, data, children, data.key, Ctor); } /** * Create component virtual node componentVnode */ function createComponent(vm, tag, data, children, key, Ctor) { let componentVnode; // todo... return componentVnode; }
Note the value of Ctor:
- It will first search for local components on the vm.$options.components object. If it finds Ctor, it will be an object; (because local component definitions will not be processed by Vue.extend to become component constructors)
- If not found, it will continue to find the global component on the chain, and the Ctor at this time will be a function; (because the global component will call Vue.extend internally to process and become the component constructor)
Therefore, in createComponent, when Ctor is an object, it needs to be processed as a component constructor through Vue.extend first;
4. createComponent method implementation
Extend vnode structure
First, you need to extend the vnode structure and add component options componentOptions:
/** * Create component virtual node componentVnode */ function createComponent(vm, tag, data, children, key, Ctor) { if(isObject(Ctor)){ // todo: Get Vue.extend, and process the object as the constructor of the component } // When creating a vnode, the component has no text and needs to pass in undefined let componentVnode = vnode(vm, tag, data, children, key, undefined, Ctor); return componentVnode; } // options: It may be the constructor of the component, or it may be an object function vnode(vm, tag, data, children, key, text, options) { return { vm, // who instance tag, // Label data, // data children, // son key, // logo text, // text componentOptions: options // Component options, including Ctor and other extensions } }
How to get Vue.extend
- In order to facilitate the subsequent use of Vue.extend, when initializing, save Vue to Vue.options._base;
// src/global-api/index.js export function initGlobalAPI(Vue) { Vue.options = {}; // When the component is initialized, it will use Vue.options and component options to merge; // In this process, _base will also be merged into the options of the component; // After that, all vm.$options can get _base which is Vue; // In this way, Vue can be obtained by visiting vm.$options._base anywhere; Vue.options._base = Vue; Vue.options.components = {}; Vue.extend = function (opt) { } Vue.component = function (id, definition) { } }
- When the component is initialized, Vue.options will be merged with the options of the component, and _base will also be merged into the options of the component during this process;
Vue.prototype._init = function (options) { const vm = this; // Merge using Vue's options and component's own options vm.$options = mergeOptions(vm.constructor.options, options); initState(vm); if (vm.$options.el) { vm.$mount(vm.$options.el) } }
In this way, all vm.$options can get _base which is Vue;
/** * Create component virtual node componentVnode */ function createComponent(vm, tag, data, children, key, Ctor) { if(isObject(Ctor)){ // Get Vue and process the object into the component's constructor through Vue.extend Ctor = vm.$options._base.extend(Ctor) } // When creating a vnode, the component has no text and needs to pass in undefined let componentVnode = vnode(vm, tag, data, children, key, undefined, Ctor); return componentVnode; }
Therefore, all components must pass the Vue.extend method to generate the component's constructor
- Global components: processed by Vue.extend inside Vue.component;
- Local components: When createComponent creates component virtual nodes, it is processed by Vue.extend;
Extended component options componentOptions
- Components have no children, and "children of components" are slots, so children should be placed in componentOptions component options;
- When it is a component, the data data also belongs to the component, and it also needs to be placed in the componentOptions component option...
- The complete componentOptions should include: Ctor, propsData, listeners, tag, children;
function createComponent(vm, tag, data, children, key, Ctor) { if(isObject(Ctor)){ Ctor = vm.$options._base.extend(Ctor) } // Note: The component has no children, the child of the component is the slot, put children in the option of the component let componentVnode = vnode(vm, tag, data, undefined, key, undefined, {Ctor, children, tag}); return componentVnode; }
Remark: The unique identifier for a component virtual node shall be: vue-component-cid-name - cid: the unique identifier of the component instance; - name: in the component definition name Attributes;
5. Test component virtual node generation
componentVnode is the virtual node of the component;
Among them, the componentOptions option contains the constructor Ctor of the component;
Four, the end
This article introduces the component part - the compilation of components, mainly involving the following parts:
- Component compilation process introduction: html->render->vnode
- Create component virtual node: createComponent
Next, component part - component life cycle;