前端組件化其實(shí)是個(gè)持續(xù)了很長(zhǎng)時(shí)間的過程了波岛,從最開始的jquery的插件開始娄琉。我們就在封裝一些js匿沛,css淀衣,html來方便下次的復(fù)用了澎媒⌒基本各個(gè)公司都有自己的組件庫劫灶,比如我在的點(diǎn)評(píng)就是npm的組件∠忌ィ現(xiàn)在組里隨著一些項(xiàng)目往React上轉(zhuǎn)劲妙,React的組件化工作也在不斷的整理中湃鹊。
組件化的過程中出現(xiàn)了一些普遍的痛點(diǎn)。Web components解決了其中的一些棘手的問題镣奋,在這里介紹一下Web Components的一些現(xiàn)狀币呵。
問題
組件化的過程中主要遇到的一些問題:
- 資源分來管理,重用不便
- 缺乏封裝
- 缺乏移植性
首先侨颈,js余赢,css和html基本是分開放置的芯义,我們想要重用某塊功能的時(shí)候,我們需要在我們的js里面想辦法引入需要的js没佑;然后得根據(jù)組件要求的DOM結(jié)構(gòu)引入HTML毕贼;然后在我們的css里引入組件需要的css,這其實(shí)是個(gè)很痛苦的過程蛤奢。二是缺乏封裝鬼癣,尤其體現(xiàn)在CSS上,CSS一直都是全局的啤贩,沒有局部CSS的概念待秃,雖然可以通過Css Modules來模擬,但是還是有一些限制的痹屹,下文也會(huì)進(jìn)行比較章郁。三是組件缺乏移植性,就比如遷到了react的環(huán)境志衍。當(dāng)然我們可以在webpack上做一些文章暖庄,讓以前的組件支持在React項(xiàng)目中使用÷シ荆可是React自有自己的一套狀態(tài)管理的機(jī)制培廓,很多組件我們還是要重寫的,將來切換到新的環(huán)境春叫,遷移是有很大代價(jià)的肩钠。Web Components能夠解決上面的這些問題
Web Components簡(jiǎn)介
Web Components它本身不是一個(gè)規(guī)范,他是由W3C提出的另外4個(gè)規(guī)范的合集暂殖。這四個(gè)規(guī)范是:
- Shadow Dom(草案階段)
- Custom Elements(草案階段)
- HTML Template(html5)
- HTML Imports(草案階段)
如上价匠,這四個(gè)規(guī)范除了template已經(jīng)成為了HTML5的規(guī)范,其他3個(gè)還是處于草案階段的呛每,所以瀏覽器的支持情況比較差也是可以理解的了踩窖。接下來這四個(gè)規(guī)范一個(gè)個(gè)聊一聊。
Shadow Dom
他的作用是:管理多DOM樹的層級(jí)關(guān)系莉给,更好的合成DOM毙石。他的中心思想是封裝一個(gè)完全獨(dú)立于文檔流的子DOM樹。他完美的做到了css的封裝
颓遏。當(dāng)然還有文檔內(nèi)容的封裝徐矩。以及通過重定向事件做了事件層面的封裝。當(dāng)然封裝是他提供的能力叁幢,作為使用者的我們其實(shí)很關(guān)心的是主文檔與Shadow Dom的交互滤灯,這個(gè)在下面會(huì)提到。
創(chuàng)建Shadow Dom
使用的第一步是創(chuàng)建,Shadow Dom的創(chuàng)建得基于一個(gè)文檔中已經(jīng)存在的一個(gè)元素鳞骤,也就是宿主元素窒百。然后通過createShadowRoot方式創(chuàng)建。注意宿主元素的內(nèi)容是不會(huì)被渲染的豫尽。
內(nèi)容的傳遞
我們可以通過content元素來將內(nèi)容映射到shadow dom中來顯示篙梢。還可以通過設(shè)置select的屬性來決定那一塊被映射。使用如下:
<!-- 頁面 -->
<div id="nameTagTwo">
<div class="first">Bob</div>
<div>B. Love</div>
<div class="email">bob@</div>
</div>
<!-- shadow dom -->
<div style="color: red;">
<content select=".first"></content>
</div>
<div style="color: yellow;">
<content select="div"></content>
</div>
<div style="color: blue;">
<content select=".email"></content>
</div>
樣式的影響
Shadow Dom的樣式被完全封裝美旧,內(nèi)部的樣式對(duì)外部完全沒有影響渤滞。文檔流中的樣式也對(duì)內(nèi)部沒有影響。這一點(diǎn)其實(shí)很重要榴嗅。因?yàn)橹挥羞@樣妄呕,才能保證組件的無傷。但是我們使用過程中肯定也會(huì)想要有時(shí)對(duì)shadow dom中的樣式進(jìn)行一定的改寫的嗽测。Shadow Dom提供了這樣的接口绪励。
組件->影響文檔流
組件內(nèi)部只能使用:host
來改變宿主元素的樣式,頁面的其他內(nèi)容也是無法影響的
:host(x-foo:host) {
/* 當(dāng)宿主是 <x-foo> 元素時(shí)生效唠粥。 */
}
:host(div) { {
/* 當(dāng)宿主或宿主的祖先元素是
<div> 元素時(shí)生效疏魏。 */
}
文檔流->組件
文檔流可以通過::shadow或者/deep/來影響組件的樣式。如果想要修整content元素的樣式晤愧,使用::content蠢护。chrome自己使用了<<和<<<,不過這里就不討論了养涮。
::shadow和/deep/
<style>
#host ::shadow span {
color: red;
}
#host /deep/ span {
color: red;
}
</style>
/*為了與content一起使用的話::content*/
Shadom Dom與CSS modules
Shadow Dom的CSS封裝很好的解決了現(xiàn)在CSS的一個(gè)大問題,因?yàn)镃SS目前還是全局作用域的眉抬。所以CSS的模塊化還是通過各個(gè)技術(shù)hack來實(shí)現(xiàn)」嵯牛現(xiàn)在有兩種:
- 放棄CSS,使用js或json寫樣式蜀变。缺點(diǎn):放棄css處理器悄谐;JS語法寫CSS。代表:Radium库北,jsxstyle爬舰,react-style。實(shí)現(xiàn):行內(nèi)屬性
- CSS編寫寒瓦,使用JS來管理樣式依賴情屹。缺點(diǎn):得與webpack綁定使用。代表:CSS Modules杂腰,Vuejs垃你。實(shí)現(xiàn):生成獨(dú)特的className(文件名—類名—hash值)
上面的兩種是目前的CSS Module的實(shí)現(xiàn)方式,實(shí)際上都是一種hack。Shadow Dom實(shí)際上才是CSS模塊化的完美解決方案惜颇。
事件的封裝
Shadow Dom對(duì)于事件通過在冒泡階段target的重定向來封裝事件皆刺,然后一些可能對(duì)頁面造成影響的事件,Shadow Dom就會(huì)影藏掉這些事件凌摄,也就是在冒泡到主頁面的過程中被擋住了羡蛾。
就像圖中所示,普通點(diǎn)擊時(shí)锨亏,target會(huì)是我們真正點(diǎn)擊的元素痴怨,而Shadow Dom則會(huì)將事件的target重定向到宿主元素身上,主要是為了保證組件內(nèi)部的封裝屯伞。多層宿主的時(shí)候我也試過了腿箩,每層都會(huì)重定向到自己的宿主身上。
還有一些事件不會(huì)冒泡到主文檔流:abort, error, select, change, load, loadedmetadata, reset, resize,scroll and selectstart劣摇。
Shadom Dom與Virtual Dom的比較
Shadow Dom是W3c的規(guī)范珠移,它主要被我們用來處理Dom樹之間的關(guān)系,他的主要思想是封裝末融。它本身還是Dom钧惧。
Virtual Dom是React的一個(gè)選擇,它被用來作為高性能的保證勾习。高性能的地方主要體現(xiàn)在浓瞪,我們平常操作Dom的時(shí)候很多時(shí)候刷新操作就是將一塊HTML替換,我們的操作會(huì)觸發(fā)大量的repaint和reflow巧婶,這些操作都是很耗瀏覽器性能的乾颁。Virtual Dom將這些操作打包,并且通過一些Diff算法來得出如何通過最簡(jiǎn)單的方式改變成我們想要的模樣艺栈。它本身是Dom的一層抽象英岭,不是真實(shí)的Dom。
Custom Elements
這個(gè)的一大好處在于拯救了我們還是很缺乏語義化能力的文檔湿右。雖然HTML5已經(jīng)在語義化的道路上走的很遠(yuǎn)诅妹。但是文檔的語義化能力其實(shí)還是很薄弱。尤其是現(xiàn)在單頁應(yīng)用比較多毅人。很多頁面只聲明了一個(gè)入口的div吭狡。而框架為了更大的兼容性,往往生成的就是一堆簡(jiǎn)單的div丈莺。
Custom Elements提供了我們自定義元素的能力划煮。當(dāng)然,語義化并不是關(guān)鍵场刑,關(guān)鍵是它還提供了我們對(duì)語義化元素綁定功能的能力般此。
Custom Elements--創(chuàng)建
3種方式:
申明 + registerElement
registerElement + new + appendChild
createElement + appendChild
不細(xì)講了蚪战,看下面的例子,很容易理解铐懊。名字要求是必須以連字符連接的邀桑,這個(gè)在未來還可以當(dāng)做作用域來限制。
<x-foo class="x-foo"></x-foo>
<script>
document.querySelector('x-foo').addEventListener('click',function(){
alert('文檔中申明出來的');
})
var xFoo = document.createElement('x-foo');
xFoo.addEventListener('click', function(e) {
alert('直接create出來的');
});
document.body.appendChild(xFoo);
var XFoo = document.registerElement('x-foo', {
prototype: Object.create(HTMLElement.prototype)
});
var xFoo = new XFoo();
xFoo.addEventListener('click', function(e) {
alert('注冊(cè)之后科乎,new出來的');
});
document.body.appendChild(xFoo);
</script>
Custom Elements--綁定功能
其實(shí)就是很簡(jiǎn)單的通過JS的方式綁定功能壁畸。代碼如下:
var XFooProto = Object.create(HTMLElement.prototype);
// 1. 為 x-foo 創(chuàng)建 foo() 方法
XFooProto.foo = function() {
alert('foo() called');
};
// 2. 定義一個(gè)只讀的“bar”屬性
Object.defineProperty(XFooProto, "bar", {value: 5});
var XFoo = document.registerElement('x-foo', {prototype: XFooProto});
Custom Elements--生命周期
講到現(xiàn)在,我們的功能還一直是調(diào)用方在綁定茅茂。這一點(diǎn)其實(shí)蠻蠢的捏萍,我們想要得到的其實(shí)是已經(jīng)綁定了完整功能的組件。需要的是拿來立即能使用的東西空闲。這里Custom Elements提供了一些生命周期讓我們組件可以在初始化的過程中就給自己綁定上方法:
- createdCallback:創(chuàng)建元素實(shí)例
- attachedCallback:向文檔插入實(shí)例
- detachedCallback:從文檔中移除實(shí)例
- attributeChangedCallback(attrName, oldVal, newVal):添加令杈,移除,或修改一個(gè)屬性
使用的過程就像下面:
var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function() {
console.log("創(chuàng)建時(shí)觸發(fā)");
this.innerHTML = "<b>我終于有內(nèi)容了</b>";
this.addEventListener('click', function(e) {
alert('Thanks!');
});
};
proto.attachedCallback = function() {
console.log("插入文檔時(shí)觸發(fā)");
};
proto.attributeChangedCallback = function(attrName,oldVal,newVal) {
console.log(attrName, oldVal, newVal);
};
var XFoo = document.registerElement('x-foo', {prototype: proto});
var xfoo = new XFoo();
xfoo.setAttribute('value',12)
document.body.appendChild(xfoo);
Template
通過上面的Shadow Dom和Custom Elements碴倾,其實(shí)我們已經(jīng)實(shí)現(xiàn)了組件的自定義以及封裝逗噩。不過我們的模板最后一直使用的字符串。最后通過innerHtml的方式插入跌榔。包括現(xiàn)在其實(shí)JS模版其實(shí)全是這么實(shí)現(xiàn)的异雁。這樣子的壞處在于當(dāng)我們多次使用一個(gè)模板的時(shí)候(比如刷新操作),每次都得把一段字符串轉(zhuǎn)化為DOM結(jié)構(gòu)僧须,這其實(shí)是很費(fèi)瀏覽器的性能的纲刀。
Template允許我們?cè)谖臋n中申明一段HTML,他在瀏覽器的解析過程中不會(huì)有任何的副作用担平。他里面的元素img示绊,script等等都不會(huì)發(fā)請(qǐng)求。完全無害暂论。但是他又不是僅僅作為字符串存在耻台。他是被解析成了Document Fragment。這樣每次重用的時(shí)候就不會(huì)有解析為Dom這種浪費(fèi)性能的操作空另。
Html Imports
現(xiàn)在我們需要的就是將這個(gè)組件打包出去。其實(shí)一直以來關(guān)于HTML的引入就一直沒有一個(gè)好的解決方案蹋砚。嵌入iframe的話扼菠,頁面之間的交互調(diào)用其實(shí)很麻煩。用一個(gè)script標(biāo)簽埋在頁面上作為字符串也比較tricky坝咐。使用ajax來拉取html的話也會(huì)比較奇怪循榆。
除了這個(gè)問題,我們一直以來引入資源都是件比較麻煩的事情墨坚。比如我們想要引入bootstrap秧饮,我們得要手動(dòng)引入css映挂,再引入js,然后根據(jù)bootstrap提供的html結(jié)構(gòu)來使用盗尸。
Html Imports就解決了這個(gè)問題柑船,他可以用來打包資源
與優(yōu)雅的引入HTML
。
<!-- 如下 -->
<!-- <link rel="import" href=“/path/to/imports/stuff.html"> -->
使用其實(shí)很簡(jiǎn)單泼各,就是用link標(biāo)簽鞍时,也可以用js來創(chuàng)建一個(gè)link標(biāo)簽。不過這個(gè)是有同域限制的扣蜻。感覺還是得HTTP2時(shí)代逆巍,把現(xiàn)在的CDN分域問題解決了才能有使用的價(jià)值。
Web Components的兼容性
如圖莽使,chrome算是很激進(jìn)了锐极,安卓也得是比較新的版本。safari芳肌,IE灵再,F(xiàn)F的支持都很差。這里還有一張圖:
這張圖的意思的FF已經(jīng)把Custom Elements和Shadow Dom立了development flag庇勃,將會(huì)去實(shí)現(xiàn)他檬嘀。而Html Imports暫時(shí)hold on。這個(gè)和Safari暫時(shí)hold住了Custom Elements和Html Imports的原因一樣责嚷。他們都覺得這個(gè)和ES6的modules解決的是同一個(gè)問題鸳兽。他們?cè)诘却鼸S6的modules的實(shí)施效果。而最新的IE的申明則是罕拂,這三個(gè)規(guī)范都在思考中揍异,應(yīng)該是都會(huì)去實(shí)現(xiàn)。
Web Components的polyfill
也就是說這些個(gè)規(guī)范我們想單純的使用時(shí)沒有辦法的爆班。但是他是有組織提供了polyfill的衷掷。這個(gè)polyfill還是有很大的問題的,IE只能支持到IE11柿菩,而且shadow dom的CSS封裝沒有官方的支持也是沒法完美實(shí)現(xiàn)的戚嗅。
相關(guān)的框架
他的相關(guān)框架有Polymer,X-Tag枢舶,SKATEJS懦胞,Bosonic,這四個(gè)框架大部分都是對(duì)他的API的友好封裝凉泄。Polymer是google出品躏尉,目前也是有15000的star的,比較火后众,除了封裝了API胀糜,他還像Angular一樣做了一層數(shù)據(jù)的雙向綁定颅拦。但是這幾個(gè)框架都是使用的上面的Polyfill,所以上面提高的問題他們也都有教藻。
Web Components與React
這里我想比較一下這兩者距帅。因?yàn)镽eact官網(wǎng)文檔專門有一篇解釋他們兩者解決的是不同的問題。
Web Components個(gè)人感覺是HTML提出的模塊化怖竭,他的目的是復(fù)用web組件锥债,主要思想是封裝。
React是為了搭建交互式UI痊臭,主要是針對(duì)不同的狀態(tài)顯示不同的View哮肚,處理的是view與data同步。
React官網(wǎng)文檔也有實(shí)例如何在React中使用Web Components广匙。其實(shí)就是在ComponentDidMount的時(shí)候初始化一下Web Components允趟,很簡(jiǎn)單的使用。
Web Components與React Components
這里我還想比較一下這兩個(gè)組件系統(tǒng)鸦致,因?yàn)樗麄兤鋵?shí)有不少相似點(diǎn)的潮剪。
Web Components優(yōu)點(diǎn):
- HTML規(guī)范
- 復(fù)用性,移植性高
- css樣式隔離
- js做js的事情分唾,html做html的事情抗碰,css做css的事情(這個(gè)有待爭(zhēng)議)
React Components優(yōu)點(diǎn):
- virtual dom支持服務(wù)器渲染,seo友好(這個(gè)也有待爭(zhēng)議绽乔,因?yàn)間oogle爬蟲是可以跑js的)編寫測(cè)試方便(這個(gè)也有待爭(zhēng)議)
- 瀏覽器支持情況好弧蝇,新版本支持到IE9,v0.14支持到IE8
- 抽象做的更好折砸,組件狀態(tài)管理
Web components 與 Vuejs
這里還想提一下Vuejs看疗,因?yàn)榭碈SS Modules的時(shí)候看到Vuejs自己也實(shí)現(xiàn)了CSS的模塊化的,就去看了一下他的文檔睦授。發(fā)現(xiàn)他幾乎是實(shí)現(xiàn)了一套Web components两芳。他的組件的創(chuàng)建,注冊(cè)去枷,繼承怖辆,生命周期都和Web components很像∩径ィ看作者自己與其他框架比較的時(shí)候也說了疗隶,Vuejs和Polymer的區(qū)別就在于Vuejs不依賴于Web components,不需要polyfill翼闹。所以想使用Web components的可以去使用Vuejs。他能支持到IE9蒋纬。使用場(chǎng)景會(huì)比較多猎荠。
總結(jié)
總體來說坚弱,Web component他是w3c標(biāo)準(zhǔn),基本會(huì)是組件技術(shù)的最終方向关摇,但是需要大量的時(shí)間來讓來讓瀏覽器支持荒叶。
文章可以直接訪問我的前端網(wǎng)站來查看,平時(shí)的日常整理也都會(huì)記錄上去输虱。
轉(zhuǎn)載的話些楣,請(qǐng)注明出處
參考:
- http://www.html5rocks.com/zh/tutorials/webcomponents/shadowdom-201
- http://www.html5rocks.com/zh/tutorials/webcomponents/customelements
- http://webcomponents.org/articles/a-quick-polymer-introduction
- https://facebook.github.io/react/docs/webcomponents.html
- http://benmccormick.org/2014/08/28/custom-elements-by-example
- https://www.w3.org/standards/techs/components#w3c_all
- http://www.csdn.net/article/2015-08-11/2825439-vue