The content of the previous two sections introduced components, from the principle of components to the application of components, including the implementation and usage scenarios of asynchronous components and functional components. As we all know, components are the things that run through the entire Vue design concept, and are also the core ideas that guide our development, so in the next few articles, we will return to the content of components to do source code analysis, starting with the commonly used dynamic components, Including the principle of inline templates, the concept of built-in components will be briefly mentioned at the end, paving the way for future articles.
12.1 Dynamic Components
I believe that most of the dynamic components will be used in the development process. When we need to switch states between different components, dynamic components can meet our needs very well. The core of which is the component tag and the is attribute. use.
12.1.1 Basic usage
The example is a basic usage scenario of a dynamic component. When the button is clicked, the view switches among the components child1, child2, and child3 according to the value of this.chooseTabs.
// vue <div id="app"> <button @click="changeTabs('child1')">child1</button> <button @click="changeTabs('child2')">child2</button> <button @click="changeTabs('child3')">child3</button> <component :is="chooseTabs"> </component> </div> // js var child1 = { template: '<div>content1</div>', } var child2 = { template: '<div>content2</div>' } var child3 = { template: '<div>content3</div>' } var vm = new Vue({ el: '#app', components: { child1, child2, child3 }, methods: { changeTabs(tab) { this.chooseTabs = tab; } } })
12.1.2 AST parsing
The interpretation of <component> is consistent with the previous ones. It will start from the AST parsing stage. The process will not focus on every detail, but will specifically explain the differences from the previous processing methods. For the differences in dynamic component parsing, focus on processComponent. Due to the existence of the is attribute on the label, it will mark the component attribute on the final ast tree.
// Parsing for dynamic components function processComponent (el) { var binding; // Get the value corresponding to the is attribute if ((binding = getBindingAttr(el, 'is'))) { // There are more component attributes on the ast tree el.component = binding; } if (getAndRemoveAttr(el, 'inline-template') != null) { el.inlineTemplate = true; } }
The final ast tree is as follows:
12.1.3 render function
With the ast tree, the next step is to generate an executable render function according to the ast tree. Due to the component attribute, the generation process of the render function will take the genComponent branch.
// render function generating function var code = generate(ast, options); // Implementation of generate function function generate (ast,options) { var state = new CodegenState(options); var code = ast ? genElement(ast, state) : '_c("div")'; return { render: ("with(this){return " + code + "}"), staticRenderFns: state.staticRenderFns } } function genElement(el, state) { ··· var code; // Dynamic component branching if (el.component) { code = genComponent(el.component, el, state); } }
The processing logic for dynamic components is actually very simple. When there is no inline template flag (will be described later), the subsequent child nodes are obtained for splicing. The only difference from ordinary components is that the first parameter of _c is no longer a The specified string, but a variable representing the component.
// Handling of dynamic components function genComponent ( componentName, el, state ) { // children is null when having the inlineTemplate property var children = el.inlineTemplate ? null : genChildren(el, state, true); return ("_c(" + componentName + "," + (genData$2(el, state)) + (children ? ("," + children) : '') + ")") }
12.1.4 Comparison of Ordinary Components and Dynamic Components
In fact, we can compare the difference between ordinary components and dynamic components in the render function, and the results are clear at a glance.
The render function of a normal component
"with(this){return _c('div',{attrs:{"id":"app"}},[_c('child1',[_v(_s(test))])],1)}"
The render function of the dynamic component
"with(this){return _c('div',{attrs:{"id":"app"}},[_c(chooseTabs,{tag:"component"})],1)}"
In a nutshell, the difference between dynamic components and normal components is:
-
- The component attribute has been added to the ast stage, which is the sign of a dynamic component
-
- In the stage of generating the render function, due to the existence of the component attribute, the genComponent branch will be executed, and the genComponent will perform special processing for the execution function of the dynamic component. Unlike ordinary components, the first parameter of _c is no longer a constant string. Instead, specify the component name variable.
-
- The process from render to vnode is the same as that of ordinary components, except that the string is replaced by a variable, and there is a data attribute of { tag: 'component' }. In the example, chooseTabs takes child1 at this time.
With the render function, the next process from vnode to real node is basically the same as that of ordinary components in terms of process and ideas. At this stage, you can review the analysis of the component process introduced earlier.
12.1.5 Doubt
Because my understanding of the source code is not thorough enough, after reading the creation process of dynamic components, I have a question in my heart. From the process analysis of the principle, the core of dynamic components is actually the keyword is, which is used in the compilation stage as the component attribute. This component is defined as a dynamic component, and the component as a label does not seem to have a particularly large use. As long as there is the is keyword, the component label name can be set to any custom label to achieve the effect of a dynamic component? (componenta, componentb). This string exists only in the data property of the vnode in the form of { tag: 'component' }. Does that mean that the so-called dynamic components are only due to the unilateral limitation of is? What is the meaning of the component tag? (Ask the big guy!!)
12.2 Inline Templates
Since dynamic components can have inline-template as configuration in addition to is as a value, the principle and design idea of inline-template in Vue can be clarified on this premise. Vue has a striking statement on the official website, reminding us that inline-template will make the scope of the template more difficult to understand. Therefore, it is recommended to use the template option as much as possible to define templates, rather than inline templates. Next, we use the source code to locate the reason why the so-called scope is difficult to understand.
Let's simply adjust the above example and start from the perspective of use:
// html <div id="app"> <button @click="changeTabs('child1')">child1</button> <button @click="changeTabs('child2')">child2</button> <button @click="changeTabs('child3')">child3</button> <component :is="chooseTabs" inline-template> <span>{{test}}</span> </component> </div> // js var child1 = { data() { return { test: 'content1' } } } var child2 = { data() { return { test: 'content2' } } } var child3 = { data() { return { test: 'content3' } } } var vm = new Vue({ el: '#app', components: { child1, child2, child3 }, data() { return { chooseTabs: 'child1', } }, methods: { changeTabs(tab) { this.chooseTabs = tab; } } })
The effect achieved in the example is consistent with the first example of the article. Obviously, the biggest difference from the previous cognition is that the environment in the parent component can access the environment variables inside the child component. At first glance it seemed incredible. Let's recall the previous situation where parent components can access child components. There are two major directions:
- 1. Using the event mechanism, the child component informs the parent component of the status of the child component through the $emit event, so that the parent can access the child. - 2. Using the scope slot method, the child's variables are passed to the parent in the form of props, and the parent receives it through the syntactic sugar of v-slot, and the result of our previous analysis is that this method is essentially through The form of event dispatch to notify the parent component.
In the previous analysis process, it was also mentioned that the parent component cannot access the variables of the child environment. The core reason is that: all the content in the parent template is compiled in the parent scope; all the content in the child template is compiled in the child compiled in scope. So we have reason to guess, does the inline template violate this principle and let the parent's content be compiled in the child component creation process? Let's look down:
Back to the ast parsing stage, as analyzed earlier, the key to the parsing of dynamic components is the processing of the is attribute by the processComponent function. Another key is the processing of inline-template, which will add the inlineTemplate attribute to the ast tree. Refer to the vue source code video explanation: into learning
// Parsing for dynamic components function processComponent (el) { var binding; // Get the value corresponding to the is attribute if ((binding = getBindingAttr(el, 'is'))) { // There are more component attributes on the ast tree el.component = binding; } // Add inlineTemplate property if (getAndRemoveAttr(el, 'inline-template') != null) { el.inlineTemplate = true; } }
Due to the existence of inlineTemplate in the render function generation stage, the child node of the parent render function is null. This step also determines that the template under the inline-template is not compiled in the parent component stage, and how the template is passed to the compilation process of the child component. Woolen cloth? The answer is that the template exists in the form of attributes, and the attribute value is obtained when the child instance is reached
function genComponent (componentName,el,state) { // children is null when having the inlineTemplate property var children = el.inlineTemplate ? null : genChildren(el, state, true); return ("_c(" + componentName + "," + (genData$2(el, state)) + (children ? ("," + children) : '') + ")") }
Let's look at the result of the final render function, where the template exists in the parent component's inlineTemplate property in the form of {render: function(){...}}.
"_c('div',{attrs:{"id":"app"}},[_c(chooseTabs,{tag:"component",inlineTemplate:{render:function(){with(this){return _c('span',[_v(_s(test))])}},staticRenderFns:[]}})],1)"
The final vnode result also shows that the inlineTemplate object will remain in the data property of the parent component.
// vnode result { data: { inlineTemplate: { render: function() {} }, tag: 'component' }, tag: "vue-component-1-child1" }
After having vnode, we come to the last key step, the process of generating real nodes according to vnode. Starting from the root node, when vue-component-1-child1 is encountered, it will go through the process of instantiating and creating child components. Before instantiating child components, the inlineTemplate property will be processed.
function createComponentInstanceForVnode (vnode,parent) { // Default options for child components var options = { _isComponent: true, _parentVnode: vnode, parent: parent }; var inlineTemplate = vnode.data.inlineTemplate; // Inline template processing, get the render function and staticRenderFns respectively if (isDef(inlineTemplate)) { options.render = inlineTemplate.render; options.staticRenderFns = inlineTemplate.staticRenderFns; } // Execute vue child component instantiation return new vnode.componentOptions.Ctor(options) }
The default option configuration of the subcomponent will get the render function of the template according to the inlineTemplate property on the vnode. At this point in the analysis, the conclusion is clear. The content of an inline template is eventually parsed in the child component, so it's not surprising that the scope of the child component is available in the template.
12.3 Built-in components
Finally, let’s talk about another concept in Vue’s thinking, built-in components. In fact, the official documentation of Vue lists the built-in components, namely component, transition, transition-group, keep-alive, slot, where <slot> we are inserting The slot section has been introduced in detail, and the component use section also spends a lot of space to analyze from use to principle. However, after learning slot and component, I began to realize that slot and component are not really built-in components. Built-in components are components that have been registered globally during the source code initialization phase. And <slot> and <component> are not treated as a component, so there is no component life cycle. The slot will only be converted into the renderSlot function for processing in the render function stage, and the component will only use the is attribute to convert the first parameter of createElement from a string to a variable, that's all. Therefore, returning to the conceptual understanding, the built-in components are the components provided by the source code itself, so the focus of this part will be on when the built-in components are registered and what are the differences during compilation. This part is just an introduction. Next, there will be two articles to introduce the implementation principles of keep-alive, transition, and transition-group in detail.
12.3.1 Constructor-Defined Components
In the initialization phase of Vue, three component objects will be added to the components property of the constructor. The writing method of each component object is consistent with the writing method of our custom component process. It has a render function, a life cycle, and various data are also defined.
// keep-alive component options var KeepAlive = { render: function() {} } // transition component options var Transition = { render: function() {} } // transition-group component options var TransitionGroup = { render: function() {}, methods: {}, ··· } var builtInComponents = { KeepAlive: KeepAlive }; var platformComponents = { Transition: Transition, TransitionGroup: TransitionGroup }; // Option configuration of Vue constructor, merging of components options extend(Vue.options.components, builtInComponents); extend(Vue.options.components, platformComponents);
We mentioned the extend method at the beginning of the series when analyzing the options for merging. The attributes on the object are merged into the source object, and the attributes are overwritten if they are the same.
// Merge the _from object into the to object. When the attributes are the same, the attributes of the to object are overwritten function extend (to, _from) { for (var key in _from) { to[key] = _from[key]; } return to }
Finally, the Vue constructor has configuration options for three components.
Vue.components = { keepAlive: {}, transition: {}, transition-group: {}, }
12.3.2 Registering built-in components
Just having a definition is not enough. Components need to be used globally and have to be registered globally. In the initialization process of a Vue instance, the most important first step is to merge options, and resource class options like built-in components will have a special option merging strategy, and the component options on the final constructor will be registered in the form of the prototype chain. In the instance's compoonents option (same for directives and filters).
// resource options var ASSET_TYPES = [ 'component', 'directive', 'filter' ]; // Define a strategy for resource consolidation ASSET_TYPES.forEach(function (type) { strats[type + 's'] = mergeAssets; // Define Default Policy }); function mergeAssets (parentVal,childVal,vm,key) { var res = Object.create(parentVal || null); // Create an empty object with parentVal as the prototype if (childVal) { assertObjectType(key, childVal, vm); // components,filters,directives options must be objects return extend(res, childVal) // Subclass option assignment to empty object } else { return res } }
The two key steps are var res = Object.create(parentVal || null);, which creates an empty object with parentVal as the prototype, and finally copies the user-defined component options to the empty object through extend. After the options are merged, the built-in components are also registered globally.
{ components: { child1, __proto__: { keepAlive: {}, transition: {}, transitionGroup: {} } } }
Finally, let's see that there is no template template in the built-in component object, but the render function. In addition to reducing the performance-consuming template parsing process, I think the important reason is that the built-in component does not have a rendered entity. Finally, let's look forward to the follow-up analysis of the principles of keep-alive and transition, so stay tuned.