RegularJS - 组件组合实例详解

提示:阅读本文需配合Regularjs官方文档,一起食用味道更佳哦!

组件是什么?组件的定义为:

在用户端开发领域,组件应该是一种独立的、可复用的交互元素的封装。

RegularJS的组件满足以下等式:

组件 = 模板 + 数据 + 业务逻辑

如果说组件是Regular的灵魂,那么组合是Regular灵魂的核心。那么组合到底是什么?

简单来说,组合的实质就是让组件在即使在与其他组件嵌套使用时仍保持对自身元素的控制。我们需要知道的是如何保持控制,接下来以简单的插线板DEMO为例,深入理解组件组合的概念。

本文涉及两个概念:
- 引入声明,即{#include}
- 内嵌内容,即this.$body

下面以上面链接中的DEMO代码为例理解这两个概念的使用。

先大致说一下插线板DEMO的设计思路:以日常使用的插线板为例,插线板有一个面板、一个开关和若干个插孔,我们把面板(panel组件)、开关(on_off组件)和插孔(jack组件)分别看做独立的组件,然后插线板就是将这些小组件组合在一起使用的主程序(container)。面板(panel组件)负责把各组件集成在自身,插孔(jack组件)由主程序(container)根据开关(on_off组件)状态控制是否通电:当开关处于开启状态时,各插孔通电;当面板开关处于关闭状态时,各插孔断电。

将代码模板抽象出来如下:

<!-- 插孔模板 -->
<script type="template/regular" id="jack">
    <div class="u-panel1">
        <p class="u-panel1-title">插孔</p>
        <p class="u-jack-panel">
            <span class="u-jack-top">||</span>
            <span class="u-jack-left">\\</span>
            <span class="u-jack-right">//</span>
        </p>
    </div>
</script>

<!-- 开关模板 -->
<script type="template/regular" id="on_off">
    <div class="u-panel2">
        <a href="javascript:" class="u-switch" on-click={this.switchStatus()}><span ref="on">on</span> / <span ref="off" class="selected">off</span></a>
    </div>
</script>

<!-- 控制面板模板 -->
<script type="template/regular" id="panel">
    <div class="container">
        <p>我是一个插线板</p>
        {content}
    </div>
</script>

然后将对应的模板声明和方法完成如下:

<!-- 主程序 -->
<script>
    var jack = Regular.extend({
        name: 'jack',
        template: document.querySelector("#jack").innerHTML
    })

    var on_off = Regular.extend({
        name: 'on_off',
        template: document.querySelector("#on_off").innerHTML,
        switchStatus: function () {
            var refs = this.$refs;
            refs.on.className = refs.on.className === '' ? 'selected' : '';
            refs.off.className = refs.off.className === 'selected' ? '' : 'selected';
            this.$emit("statusChange");
        }
    })

    var panel = Regular.extend({
        name: 'panel',
        template: document.querySelector("#panel").innerHTML
    })

    var container = new Regular({
        template:
        '<panel content={content}></panel>',
        data: {
            content: 
            '<jack ref="jack1"></jack>\
             <jack ref="jack2"></jack>\
             <jack ref="jack3"></jack>\
             <on_off on-statusChange={this.checkStatus()}></on_off>'
        },
        checkStatus: function(){
            var dom = Regular.dom;
            var selectItem = dom.find('.selected');
            var panels = document.getElementsByClassName('u-jack-panel');
            if(selectItem.innerText === 'on') {
                for(var i = 0; i < panels.length; i ++) {
                    panels[i].className += ' active';
                }
            }
            if(selectItem.innerText === 'off') {
                for(i = 0; i < panels.length; i ++) {
                    panels[i].className = 'u-jack-panel';
                }
            }
        }
    }).$inject("#app")
</script>

在上面的代码模板中,我们可以清晰地看到插线板主要由3部分组件组成:插孔(jack组件)、开关(on_off组件)和控制面板(panel组件),其中控制面板集成了开关和插孔组件。我们注意到,在控制面板模板上有一个插值语句 {content},该语句为主程序调用该组件模板需要传入的参数。通过这样的插值方式,无法达到我们显示插线板效果的目的,运行效果见错误示范1。可以发现,我们传入的HTML结构在这里被解析成了字符串直接显示,各组件模板也没有正常展示。

故此处使用文章开头提到的组合中的第一个概念 —— 引入声明,通过{#include} 进行插值来传入模板片段。

接着,我们对该模板通过 {#include content} 引入声明,代码如下:

<!-- 控制面板模板 -->
<script type="template/regular" id="panel">
    <div class="container">
        <p>我是一个插线板</p>
        {#include content}
    </div>
</script> 

运行上面的代码,点击on / off开关按钮,会发现开关只会完成自身开关切换的转换,插孔状态并没有变化,运行效果见错误示范2。那么为什么会出现这样的情况呢?我们对页面进行检查,会发现控制台报错 c.checkStatus is not a function,也就是我们在主程序里编辑的checkStatus()函数并没有调用,那是谁在调用这部分内容?我们做一个测试,在panel的声明中添加一个函数,该声明被改成下面的代码:

var panel = Regular.extend({
    name: 'panel',
    template: document.querySelector("#panel").innerHTML,
    checkStatus: function(){
        alert("I shouldn't be activated here!");
    }
})

然后再运行主程序,会发现点击按钮后不报错了,直接弹出上面修改代码中的提示,运行效果见错误示范3。这个时候我们就可以发现问题所在了,本应由主程序container调用的checkStatus()方法,现在被panel组件调用了,使用{#include content}把模板成功插入,却也改变了插入内容的上下文,container组件失去了对自身元素的控制权。为解决该问题需要引入文章开头提到的第二个概念 —— 内嵌内容(transcluded content)

使用内嵌内容对上面控制面板对应的部分再次进行修改(注:两处修改对应的地方不同):

<!-- 控制面板模板 -->
<script type="template/regular" id="panel">
    <div class="container">
        <p>我是一个插线板</p>
        // 引入内嵌内容
        {#include this.$body}
    </div>
</script>

// 修改代码声明
<script>
    var container = new Regular({
        template:
        '<panel content={content}>\
             <jack ref="jack1"></jack>\
             <jack ref="jack2"></jack>\
             <jack ref="jack3"></jack>\
             <on_off on-statusChange={this.checkStatus()}></on_off>\
         </panel>',
        checkStatus: function(){
            var dom = Regular.dom;
            var selectItem = dom.find('.selected');
            var panels = document.getElementsByClassName('u-jack-panel');
            if(selectItem.innerText === 'on') {
                for(var i = 0; i < panels.length; i ++) {
                    panels[i].className += ' active';
                }
            }
            if(selectItem.innerText === 'off') {
                for(i = 0; i < panels.length; i ++) {
                    panels[i].className = 'u-jack-panel';
                }
            }
        }
    }).$inject("#app")
</script>

现在再运行代码,发现运行效果与预想的一致,运行效果见正确示范。也就是说,我们使用的this.$body在该句话运行时实际上创造出了一个独立的作用块,无论模板中的代码被插入到哪里,主程序container始终保持对该模板内容的控制,这便是this.$body的神奇之处。

至此,Regular组件的组合的使用和意义已经很清晰:组合的组件自身拥有完整的功能,组合在一起之后,总控制者对自身添加的内容拥有控制权。正如此例展示的那样:开关组件自身拥有切换状态的功能,但开关与插孔的联动始终由主程序container控制,这就是组件的组合。

猜你喜欢

转载自blog.csdn.net/Small_Wchen/article/details/78953575