• 12.1 动态组件
    • 12.1.1 基本用法
    • 12.1.2 AST解析
    • 12.1.3 render函数
    • 12.1.4 普通组件和动态组件的对比
      • 普通组件的render函数
      • 动态组件的render函数
    • 12.1.5 疑惑

    12.1 动态组件

    动态组件我相信大部分在开发的过程中都会用到,当我们需要在不同的组件之间进行状态切换时,动态组件可以很好的满足我们的需求,其中的核心是component标签和is属性的使用。

    12.1.1 基本用法

    例子是一个动态组件的基本使用场景,当点击按钮时,视图根据this.chooseTabs值在组件child1,child2,child3间切换。

    1. // vue
    2. <div id="app">
    3. <button @click="changeTabs('child1')">child1</button>
    4. <button @click="changeTabs('child2')">child2</button>
    5. <button @click="changeTabs('child3')">child3</button>
    6. <component :is="chooseTabs">
    7. </component>
    8. </div>
    1. // js
    2. var child1 = {
    3. template: '<div>content1</div>',
    4. }
    5. var child2 = {
    6. template: '<div>content2</div>'
    7. }
    8. var child3 = {
    9. template: '<div>content3</div>'
    10. }
    11. var vm = new Vue({
    12. el: '#app',
    13. components: {
    14. child1,
    15. child2,
    16. child3
    17. },
    18. methods: {
    19. changeTabs(tab) {
    20. this.chooseTabs = tab;
    21. }
    22. }
    23. })

    12.1.2 AST解析

    <component>的解读和前面几篇内容一致,会从AST解析阶段说起,过程也不会专注每一个细节,而是把和以往处理方式不同的地方特别说明。针对动态组件解析的差异,集中在processComponent上,由于标签上is属性的存在,它会在最终的ast树上打上component属性的标志。

    1. // 针对动态组件的解析
    2. function processComponent (el) {
    3. var binding;
    4. // 拿到is属性所对应的值
    5. if ((binding = getBindingAttr(el, 'is'))) {
    6. // ast树上多了component的属性
    7. el.component = binding;
    8. }
    9. if (getAndRemoveAttr(el, 'inline-template') != null) {
    10. el.inlineTemplate = true;
    11. }
    12. }

    最终的ast树如下:

    12.1 动态组件 - 图1

    12.1.3 render函数

    有了ast树,接下来是根据ast树生成可执行的render函数,由于有component属性,render函数的产生过程会走genComponent分支。

    1. // render函数生成函数
    2. var code = generate(ast, options);
    3. // generate函数的实现
    4. function generate (ast,options) {
    5. var state = new CodegenState(options);
    6. var code = ast ? genElement(ast, state) : '_c("div")';
    7. return {
    8. render: ("with(this){return " + code + "}"),
    9. staticRenderFns: state.staticRenderFns
    10. }
    11. }
    12. function genElement(el, state) {
    13. ···
    14. var code;
    15. // 动态组件分支
    16. if (el.component) {
    17. code = genComponent(el.component, el, state);
    18. }
    19. }

    针对动态组件的处理逻辑其实很简单,当没有内联模板标志时(后面会讲),拿到后续的子节点进行拼接,和普通组件唯一的区别在于,_c的第一个参数不再是一个指定的字符串,而是一个代表组件的变量。

    1. // 针对动态组件的处理
    2. function genComponent (
    3. componentName,
    4. el,
    5. state
    6. ) {
    7. // 拥有inlineTemplate属性时,children为null
    8. var children = el.inlineTemplate ? null : genChildren(el, state, true);
    9. return ("_c(" + componentName + "," + (genData$2(el, state)) + (children ? ("," + children) : '') + ")")
    10. }

    12.1.4 普通组件和动态组件的对比

    其实我们可以对比普通组件和动态组件在render函数上的区别,结果一目了然。

    普通组件的render函数

    1. "with(this){return _c('div',{attrs:{"id":"app"}},[_c('child1',[_v(_s(test))])],1)}"

    动态组件的render函数

    1. "with(this){return _c('div',{attrs:{"id":"app"}},[_c(chooseTabs,{tag:"component"})],1)}"

    简单的总结,动态组件和普通组件的区别在于:

    1. ast阶段新增了component属性,这是动态组件的标志
    2. 产生render函数阶段由于component属性的存在,会执行genComponent分支,genComponent会针对动态组件的执行函数进行特殊的处理,和普通组件不同的是,_c的第一个参数不再是不变的字符串,而是指定的组件名变量。
    3. rendervnode阶段和普通组件的流程相同,只是字符串换成了变量,并有{ tag: 'component' }data属性。例子中chooseTabs此时取的是child1

    有了render函数,接下来从vnode到真实节点的过程和普通组件在流程和思路上基本一致,这一阶段可以回顾之前介绍组件流程的分析

    12.1.5 疑惑

    由于自己对源码的理解还不够透彻,读了动态组件的创建流程之后,心中产生了一个疑问,从原理的过程分析,动态组件的核心其实是is这个关键字,它在编译阶段就以component属性将该组件定义为动态组件,而component作为标签好像并没有特别大的用途,只要有is关键字的存在,组件标签名设置为任意自定义标签都可以达到动态组件的效果?(componenta, componentb)。这个字符串仅以{ tag: 'component' }的形式存在于vnodedata属性存在。那是不是说明,所谓动态组件只是由于is的单方面限制?那component标签的意义又在哪里?