15

前言

上两篇Mvvm教程的热度超出我的预期,很多码友留言表扬同时希望我继续出下一篇教程,当时我也半开玩笑说只要点赞超10就兑现承诺,没想到还真破了10,所以就有了今天的文章。

准备工作

熟读
【教学向】150行代码教你实现一个低配版的MVVM库(1)- 原理篇
【教学向】150行代码教你实现一个低配版的MVVM库(2)- 代码篇

本篇是在上两篇的基础之上对代码进行进一步扩展,从而实现web component功能,所以读者务必掌握mvvm的实现机制才能深入理解本篇的内容(mvvm是web component的基石)。

什么才是好的 web component 设计

目前市面上各大主流前端框架,凡事带web component功能的,他们的设计水准基本都不入我的法眼,唯一看得上眼的是google的polymer,但是在某些API设计层面也显得略微繁琐(想了解polymer的朋友看一翻一下我专栏里面10篇polymer入门系列教程

什么是component

html提供的原生标签,比如DIV, BUTTON, INPUT家族,Hx家族等等,这些就好比俄罗斯方块里的一块块标准积木,我们称它们为stand component
2164182735-560b52e32e896

某一天这些积木不能满足你的需求了,被扩展或被组合形成了非基本形状
2061348422-560b891619854

这些新形状就是custom component,自定义组件!为什么要有component呢,好处是什么呢?

 1. 可以复用
 2. 结构清晰
 3. 独立开发
 

你稍微开动下脑筋就能分析出来了,我就不展开了。

我心目中的web component

在座的各位都写过index.html么?很简单
主要就分成3大块内容,style, dom, script

<!--样式-->
<style>
</style>
<!--DOM UI-->
<body>
</body>
<!--逻辑-->
<script>
</script>

然后外面用个<html></html>包裹

所以这期低配版web component库设计目的很简单,作为一个开发人员,我希望在写一个custom component的时候也能按照index.html的原生风格来写,这是多么的优雅,自然,没有学习成本啊!
这也应该是无数人心目中的web component设计

API设计

所以,我们的SegmentFault.js v2.0的Web Component的设计宗旨就是,尽量接近原生的html结构和使用习惯,接近原生从而把学习成本降到最低,是我追求的东西

<!-- myComp.html //文件名还是以.html结尾,自然 -->
<sf-component>
    <style>
        <!--css-->
    </style>
    <template>
        <!-- any dom-->
    <template>
    <script>
        //js
    </script>
<sf-component>

写个具体的例子

<!-- myComp.html -->
<sf-component>
    <style>
        button{
            color:red;
        }
        p{
            color:yellow;
        }
    </style>
    <template>
        <div>
            <input type="text" sf-value="this.message"/>
            <button sf-innerText="this.buttonName" onclick="this.clickHandler()"></button>
            <p sf-innerText="this.message"> 
            </p>
        </div>
    </template>
    <script>
        this.message = "this is a component";
        this.buttonName = "click me";
        this.clickHandler = function(){
            alert(this.message);
        };
    </script>
</sf-component>

一个Component的描述文件定义好了,那么接下去就是如何引入它了。沿用上篇Mvvm中的风格,我们给SegmentFault这个Class弄个registerComponent(tagName,compPath)方法,比如在index.html中

var sf = new SegmentFault();
sf.registerController("xxx",xxx);
...
sf.registerComponent("my-comp","components/myComp.html");
...
sf.init();

而在父组件中我们就可以通过"my-comp"这个我们刚刚注册时起的标签名来引入这个组件

<body>
    <div>...<div>
    <my-comp></my-comp>
</body>

怎么样!四个字:干净利落

一个Web Component库必须具备的基本素(功)养(能)

 1. Mvvm 具备双向绑定功能
 2. Shadow Style 具有独立的不污染全局的css功能
 3. Communication 具有和父子兄弟组件通讯的功能
 4. 拥有生命周期 (属于高级功能,本低配版库不涉及)

第一点Mvvm

Mvvm之前已经实现,我们只要套用之前的实现即可

第二点Shadow Style

可能很多人对这个没什么概念,我沿用前文中的内容,比如我们在component中定义了它的style,如

<!-- myComp.html -->
<sf-component>
    <style>
        button{
            color:red;
        }
        p{
            color:yellow;
        }
    </style>
    <template>
        <div>
            ...
        </div>
    </template>
    <script>
        ...
    </script>
</sf-component>

这里我们在<style></style>标签中,定义了css,其中p和button的写法在传统观念中都是会影响html页面中所有的p元素和button元素的,这是我们不希望发生的,我们希望的是这个<style></style>标签生效的作用域仅仅是在当前的,被定义的component中。这种有独立作用域的css就叫Shadow Style。

要实现Shadow Style,其实有比较简单的做法,本篇设计篇中不会涉及,你可以趁此独立思考下,待下篇看看是否与我不谋而合,或者有比我更加高级的方案。

第三点Communication

即组件之间的通讯,经常有人在sf中问到这个组件通讯问题,其实这个问题是有比较标准的答案的,即3点

 1. 父子通讯: 父->子 通过 set 属性, 子->父 抛事件
 2. 兄弟通讯: 大儿子 抛事件给 -> 父 -> set 小儿子 的属性
 3. 远亲通讯: 走消息总线 (其实就在一个单例上搞事件机制)

要实现通讯机制,其实也不复杂,主要就2个功能,1 父组件可以set子组件的属性, 2 组件可以向外层抛事件,外层也可以监听组件抛出的事件,所以,我们会如此设计这块的内容,觉个例子,代码说话

<body>
    <div>...<div>
    <my-comp sf-msg="vm.message" sf-oncustomevent="vm.customEventHandler"></my-comp>
</body>

大家注意看,从父组件的角度,我可以使用sf- + propertyName(这里是msg) 来实现外部父组件对组件的赋值,而且还能使用sf-on + 自定义事件名称(这里是customevent) 对组件进行监听。

换个角度,从子组件角度出发,我可以被外部赋值,我可以可以向外部dispatch事件。

<sf-component>
    <style>
        //...
    </style>
    <template>
        <div>
            <div class="compClass">
                <input type="text" sf-value="this.message" />
                <button sf-innerText="this.buttonName" onclick="this.clickHandler()"></button>
                <my-comp2 sf-msg="this.message"></my-comp2>
                <p sf-innerText="this.message + ', hi Component1'">
                </p>
            </div>
        </div>
    </template>
    <script>
        this.buttonName = "click me";
        this.clickHandler = function () {
            alert(this.message);
            this.dispatchEvent("customevent", "hello world");//为component的vm,内置一个dispatchEvent方法,用法和原生的事件机制一毛一样。
        };
        Object.defineProperty(this, "msg", {
            set: function (value) {
                if (value) {
                    this.message = value;
                }
            }
        });
    </script>
</sf-component>

使用Object.defineProperty可以很大程度上满足我们对set property的需求,另外再给component的vm挂载一个内置的函数this.dispatchEvent来发送自定义事件我们就功德圆满了。

第四点生命周期

你可以给一个组件:由注册->加载定义->显示到DOM Tree->内容更新->从DOM Tree移除->销毁 等一系时间节点定义他的生命周期,如果是做的比较考究的库,你可以把这这些时间节点的变更都一一向用户通知,或者提供api供用户控制。本文设计的低配版库阉割了这部分高级功能,我们就是一教学向的库,不整这些有的没的。

结语

至此,设计篇结束,主要介绍了一下本教学库的设计理念和一些web component的基本概念,欢迎点赞收藏评论,投硬笔投香蕉

如果本文阅读没有问题,请继续服用下一篇
【教学向】150行代码教你实现一个低配版的MVVM库(2)- 代码篇

相关阅读

【教学向】150行代码教你实现一个低配版的MVVM库(1)- 原理篇
【教学向】150行代码教你实现一个低配版的MVVM库(2)- 代码篇
【教学向】再加150行代码教你实现一个低配版的web component库(1) —设计篇
【教学向】再加150行代码教你实现一个低配版的web component库(2) —原理篇


熊丸子
5.6k 声望293 粉丝

现在sf的文章质量堪忧~~~