vue dynamic component source code analysis

Posted by nev25 on Wed, 26 Jan 2022 18:14:37 +0100

Basic use

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

AST analysis

  • For the differences in dynamic component parsing, focus on processComponent. Due to the existence of is attribute on the tag, 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'))) {
    // The attribute of component is added to the ast tree
    el.component = binding;
  }
  if (getAndRemoveAttr(el, 'inline-template') != null) {
    el.inlineTemplate = true;
  }
}


render function

  • The executable render function is generated 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);
  }
}


  • When there is no inline template flag (which 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 specified string, but a variable representing the component
// Processing for dynamic components
  function genComponent (
    componentName,
    el,
    state
  ) {
    // children is null when you have the inlineTemplate property
    var children = el.inlineTemplate ? null : genChildren(el, state, true);
    return ("_c(" + componentName + "," + (genData$2(el, state)) + (children ? ("," + children) : '') + ")")
  }

Comparison between ordinary components and dynamic components

To sum up, the difference between dynamic components and ordinary components is:

  • The ast phase adds the component attribute, which is the flag of dynamic components
  • In the stage of generating render function, due to the existence of component attribute, genComponent branch will be executed. genComponent will carry out special processing for the execution function of dynamic components. Unlike ordinary components_ The first parameter of c is no longer the invariant string, but the specified component name variable.
  • The process from render to vnode is the same as that of ordinary components, except that the string is replaced with a variable and has the data attribute of {tag: 'component'}. In the example, chooseTabs takes child1 at this time.
  • With the render function, the process from vnode to real node is basically the same as that of ordinary components in terms of process and idea

Inline template

  • This makes the scope difficult to understand because the parent component can access the variables of the child component
// 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 environment in the parent component can access the environment variables inside the child component

  • Using the event mechanism, the child component informs the parent component of the status of the child component through the $emit event, so as to achieve the purpose of the parent accessing the child
  • Using the scope slot method, the child's variables are passed to the parent in the form of props, and the parent receives them through the syntax sugar of v-slot. This method essentially notifies the parent component in the form of event dispatch
  • The parent content is put into the sub component creation process to compile, which is the way of inline template

Back to processComponent

  • Another key is the processing of inline template, which will add the inline template attribute to the ast tree
//  Parsing for dynamic components
  function processComponent (el) {
    var binding;
    // Get the value corresponding to the is attribute
    if ((binding = getBindingAttr(el, 'is'))) {
      // The attribute of component is added to the ast tree
      el.component = binding;
    }
    // Add inlineTemplate property
    if (getAndRemoveAttr(el, 'inline-template') != null) {
      el.inlineTemplate = true;
    }
  }

render function generation stage

  • Due to the existence of inlineTemplate, the child node of the parent render function is null. This step also determines that the template under inline template is not compiled in the parent component stage. The template exists in the form of attribute, and the attribute value is obtained when the child instance is reached
function genComponent (componentName,el,state) {
  // children is null when you have the inlineTemplate property
  var children = el.inlineTemplate ? null : genChildren(el, state, true);
  return ("_c(" + componentName + "," + (genData$2(el, state)) + (children ? ("," + children) : '') + ")")
}

The final template exists in the inlineTemplate attribute of the parent component 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 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 sub components. The inlineTemplate attribute will be processed before instantiating sub components.
  • The content of the inline template will eventually be parsed in the subcomponent
function createComponentInstanceForVnode (vnode,parent) {
    // Default options for subcomponents
    var options = {
      _isComponent: true,
      _parentVnode: vnode,
      parent: parent
    };
    var inlineTemplate = vnode.data.inlineTemplate;
    // For the processing of inline templates, get the render function and staticRenderFns respectively
    if (isDef(inlineTemplate)) {
      options.render = inlineTemplate.render;
      options.staticRenderFns = inlineTemplate.staticRenderFns;
    }
    // Execute vue subcomponent instantiation
    return new vnode.componentOptions.Ctor(options)
  }

Topics: Javascript Front-end Vue.js