Web Components是個什么樣的東西
前端組件化這個主題相關(guān)的內(nèi)容已經(jīng)火了很久很久,angular 剛出來時的 Directive 到 angular2 的 components,還有 React 的components 等等,無一不是前端組件化的一種實(shí)現(xiàn)和探索,但是提上議程的 Web Components 標(biāo)準(zhǔn)是個怎樣的東西,相關(guān)的一些框架或者類庫,如 React,Angular2,甚至是 x-tag,polymer 現(xiàn)在實(shí)現(xiàn)的組件化的東西和 Web Components 標(biāo)準(zhǔn)差別在哪里?我花時間努力地把現(xiàn)有的 W3C Web Components 文檔看了下,然后堅強(qiáng)地寫下這些記錄。
首先我們需要知道,Web Components 包括了四個部分:
- Custom Elements
- HTML Imports
- HTML Templates
- Shadow DOM
這四部分有機(jī)地組合在一起,才是 Web Components。
可以用自定義的標(biāo)簽來引入組件是前端組件化的基礎(chǔ),在頁面引用 HTML 文件和 HTML 模板是用于支撐編寫組件視圖和組件資源管理,而 Shadow DOM 則是隔離組件間代碼的沖突和影響。
下邊分別是每一部分的筆記內(nèi)容。
Custom Elements
概述
Custom Elements 顧名思義,是提供一種方式讓開發(fā)者可以自定義 HTML 元素,包括特定的組成,樣式和行為。支持 Web Components 標(biāo)準(zhǔn)的瀏覽器會提供一系列 API 給開發(fā)者用于創(chuàng)建自定義的元素,或者擴(kuò)展現(xiàn)有元素。
這一項(xiàng)標(biāo)準(zhǔn)的草案還處于不穩(wěn)定的狀態(tài),時有更新,API 還會有所變化,下邊的筆記以 Cutsom Elements 2016.02.26 這個版本為準(zhǔn),因?yàn)樵谧钚碌?chrome 瀏覽器已經(jīng)是可以工作的了,這樣可以使用 demo 來做嘗試,最后我會再簡單寫一下最新文檔和這個的區(qū)別。
registerElement
首先,我們可以嘗試在 chrome 控制臺輸入 HTMLInputElement,可以看到是有這么一個東西的,這個理解為 input DOM 元素實(shí)例化時的構(gòu)造函數(shù),基礎(chǔ)的是 HTMLElement。
Web Components 標(biāo)準(zhǔn)提出提供這么一個接口:
- document.registerElement('x-foo', {
- prototype: Object.create(HTMLElement.prototype, {
- createdCallback: {
- value: function() { ... }
- },
- ...
- })
- })
你可以使用 document.registerElement 來注冊一個標(biāo)簽,標(biāo)準(zhǔn)中為了提供 namesapce 的支持,防止沖突,規(guī)定標(biāo)簽類型(也可以理解為名字)需要使用 - 連接。同時,不能是以下這一些:
- annotation-xml
- color-profile
- font-face
- font-face-src
- font-face-uri
- font-face-format
- font-face-name
- missing-glyph
第二個參數(shù)是標(biāo)簽相關(guān)的配置,主要是提供一個 prototype,這個原型對象是以 HTMLElement 等的原型為基礎(chǔ)創(chuàng)建的對象。然后你便可以在 HTML 中去使用自定義的標(biāo)簽。如:
- <div>
- <x-foo></x-foo>
- </div>
是不是嗅到了 React 的味道?好吧,React 說它自己主要不是做這個事情的。
生命周期和回調(diào)
在這個 API 的基礎(chǔ)上,Web Components 標(biāo)準(zhǔn)提供了一系列控制自定義元素的方法。我們來一一看下:
一個自定義元素會經(jīng)歷以下這些生命周期:
- 注冊前創(chuàng)建
- 注冊自定義元素定義
- 在注冊后創(chuàng)建元素實(shí)例
- 元素插入到 document 中
- 元素從 document 中移除
- 元素的屬性變化時
這個是很重要的內(nèi)容,開發(fā)者可以在注冊新的自定義元素時指定對應(yīng)的生命周期回調(diào)來為自定義元素添加各種自定義的行為,這些生命周期回調(diào)包括了:
createdCallback
自定義元素注冊后,在實(shí)例化之后會調(diào)用,通常多用于做元素的初始化,如插入子元素,綁定事件等。
- attachedCallback
元素插入到 document 時觸發(fā)。 - detachedCallback 元素從 document 中移除時觸發(fā),可能會用于做類似 destroy 之類的事情。
- attributeChangedCallback
元素屬性變化時觸發(fā),可以用于從外到內(nèi)的通信。外部通過修改元素的屬性來讓內(nèi)部獲取相關(guān)的數(shù)據(jù)并且執(zhí)行對應(yīng)的操作。
這個回調(diào)在不同情況下有對應(yīng)不同的參數(shù):
- 設(shè)置屬性時,參數(shù)列表是:屬性名稱,null,值,命名空間
- 修改屬性時,參數(shù)列表是:屬性名稱,舊值,新值,命名空間
- 刪除屬性時,參數(shù)列表是:屬性名稱,舊值,null,命名空間
好了,就上邊了解到的基礎(chǔ)上,假設(shè)我們要創(chuàng)建一個自定義的 button-hello 按鈕,點(diǎn)擊時會 alert('hello world'),代碼如下:
- document.registerElement('button-hello', {
- prototype: Object.create(HTMLButtonElement.prototype, {
- createdCallback: {
- value: function createdCallback() {
- this.innerHTML = '<button>hello world</button>'
- this.addEventListener('click', () => {
- alert('hello world')
- })
- }
- }
- })
- })
要留意上述代碼執(zhí)行之后才能使用 <button-hello></button-hello>
擴(kuò)展原有元素
其實(shí),如果我們需要一個按鈕,完全不需要重新自定義一個元素,Web Components 標(biāo)準(zhǔn)提供了一種擴(kuò)展現(xiàn)有標(biāo)簽的方式,把上邊的代碼調(diào)整一下:
- document.registerElement('button-hello', {
- prototype: Object.create(HTMLButtonElement.prototype, {
- createdCallback: {
- value: function createdCallback() {
- this.addEventListener('click', () => {
- alert('hello world')
- })
- }
- }
- }),
- extends: 'button'
- })
然后在 HTML 中要這么使用:
- <button is="button-hello">hello world</button>
使用 is 屬性來聲明一個擴(kuò)展的類型,看起來也蠻酷的。生命周期和自定義元素標(biāo)簽的保持一致。
當(dāng)我們需要多個標(biāo)簽組合成新的元素時,我們可以使用自定義的元素標(biāo)簽,但是如果只是需要在原有的 HTML 標(biāo)簽上進(jìn)行擴(kuò)展的話,使用 is 的這種元素擴(kuò)展的方式就好。
原有的 createElement 和 createElementNS,在 Web Components 標(biāo)準(zhǔn)中也擴(kuò)展成為支持元素擴(kuò)展,例如要創(chuàng)建一個 button-hello:
- const hello = document.createElement('button', 'button-hello')
標(biāo)準(zhǔn)文檔中還有很多細(xì)節(jié)上的內(nèi)容,例如接口的參數(shù)說明和要求,回調(diào)隊(duì)列的實(shí)現(xiàn)要求等,這些更多是對于實(shí)現(xiàn)這個標(biāo)準(zhǔn)的瀏覽器開發(fā)者的要求,這里不做詳細(xì)描述了,內(nèi)容很多,有興趣的自行查閱:Cutsom Elements 2016.02.26。
和最新版的區(qū)別
前邊我提到說文檔的更新變化很快,截止至我寫這個文章的時候,最新的文檔是這個:Custom Elements 2016.07.21。
細(xì)節(jié)不做描述了,講講我看到的最大變化,就是向 ES6 靠攏。大致有下邊三點(diǎn):
- 從原本的擴(kuò)展 prototype 來定義元素調(diào)整為建議使用 class extends 的方式
- 注冊自定義元素接口調(diào)整,更加方便使用,傳入 type 和 class 即可
- 生命周期回調(diào)調(diào)整,createdCallback 直接用 class 的 constructor
前兩個點(diǎn),我們直接看下代碼,原本的代碼按照新的標(biāo)準(zhǔn),應(yīng)該調(diào)整為:
- class ButtonHelloElement extends HTMLButtonElement {
- constructor() {
- super()
- this.addEventListener('click', () => {
- alert('hello world')
- })
- }
- }
- customElements.define('button-hello', ButtonHelloElement, { extends: 'button' })
從代碼上看會感覺更加 OO,編寫上也比原本要顯得方便一些,原本的生命周期回調(diào)是調(diào)整為新的:
- constructor in class 作用相當(dāng)于原本的 createdCallback
- connectedCallback 作用相當(dāng)于 attachedCallback
- disconnectedCallback 作用相當(dāng)于 detachedCallback
- adoptedCallback 使用 document.adoptNode(node) 時觸發(fā)
- attributeChangedCallback 和原本保持一致
connect 事件和插入元素到 document 有些許區(qū)別,主要就是插入元素到 document 時,元素狀態(tài)會變成 connected,這時會觸發(fā) connectedCallback,disconnect 亦是如此。
HTML Imports
概述
HTML Imports 是一種在 HTMLs 中引用以及復(fù)用其他的 HTML 文檔的方式。這個 Import 很漂亮,可以簡單理解為我們常見的模板中的include 之類的作用。
我們最常見的引入一個 css 文件的方式是:
- <link rel="stylesheet" href="/css/master.css">
Web Components 現(xiàn)在提供多了一個這個:
- <link rel="import" href="/components/header.html">
HTMLLinkElement
原本的 link 標(biāo)簽在添加了 HTML Import 之后,多了一個只讀的 import 屬性,當(dāng)出現(xiàn)下邊兩種情況時,這個屬性為 null:
- 該 link 不是用來 import 一個 HTML 的。
- 該 link 元素不在 document 中。
否則,這個屬性會返回一個表示引入的 HTML 文件的文檔對象,類似于 document。比如說,在上邊的代碼基礎(chǔ)上,可以這樣做:
- const link = document.querySelector('link[rel=import]')
- const header = link.import;
- const pulse = header.querySelector('div.logo');
阻塞式
我們要知道的是,默認(rèn)的 link 加載是阻塞式的,除非你給他添加一個 async 標(biāo)識。
阻塞式從某種程度上講是有必要的,當(dāng)你 improt 的是一個完整的自定義組件并且需要在主 HTML 中用標(biāo)簽直接使用時,非阻塞的就會出現(xiàn)錯誤了,因?yàn)闃?biāo)簽還沒有被注冊。
document
有一點(diǎn)值得留意的是,在 import 的 HTML 中,我們編寫的 script 里邊的 document 是指向 import 這個 HTML 的主 HTML 的 document。
如果我們要獲取 import 的 HTML 的 document 的話,得這么來:
- const d = document.currentScript.ownerDocument
這樣設(shè)計是因?yàn)?import 進(jìn)來的 HTML 需要用到主 HTML 的 document。例如我們上邊提到的 registerElement。
在一個被 import 的 HTML 文件中使用下邊三個方法會拋出一個 InvalidStateError 異常:
- document.open()
- document.write()
- document.close()
對于 HTML Import,標(biāo)準(zhǔn)文檔中還有很大一部分內(nèi)容是關(guān)于多個依賴加載的處理算法的,在這里就不詳述了,有機(jī)會的話找時間再開篇談,這些內(nèi)容是需要瀏覽器去實(shí)現(xiàn)的。
HTML Templates
概述
這個東西很簡單,用過 handlebars 的人都知道有這么一個東西:
- <script id="template" type="text/x-handlebars-template">
- ...
- </script>
其他模板引擎也有類似的東西,那么 HTML Templates 便是把這個東西官方標(biāo)準(zhǔn)化,提供了一個 template 標(biāo)簽來存放以后需要但是暫時不渲染的 HTML 代碼。
以后可以這么寫了:
- <template id="template">
- ...
- </template>
接口和應(yīng)用
template 元素有一個只讀的屬性 content,用于返回這個 template 里邊的內(nèi)容,返回的結(jié)果是一個 DocumentFragment。
具體是如何使用的,直接參考官方給出的例子:
- <!doctype html>
- <html lang="en">
- <head>
- <title>Homework</title>
- <body>
- <template id="template"><p>Smile!</p></template>
- <script>
- let num = 3;
- const fragment = document.getElementById('template').content.cloneNode(true);
- while (num-- > 1) {
- fragment.firstChild.before(fragment.firstChild.cloneNode(true));
- fragment.firstChild.textContent += fragment.lastChild.textContent;
- }
- document.body.appendChild(fragment);
- </script>
- </html>
使用 DocumentFragment 的 clone 方法以 template 里的代碼為基礎(chǔ)創(chuàng)建一個元素節(jié)點(diǎn),然后你便可以操作這個元素節(jié)點(diǎn),最后在需要的時候插入到 document 中特定位置便可以了。
Template 相關(guān)的東西不多,而且它現(xiàn)在已經(jīng)是納入生效的 標(biāo)準(zhǔn)文檔 中了。
我們接下來看看重磅的 Shadow DOM。
Shadow DOM
概述
Shadow DOM 好像提出好久了,最本質(zhì)的需求是需要一個隔離組件代碼作用域的東西,例如我組件代碼的 CSS 不能影響其他組件之類的,而 iframe 又太重并且可能有各種奇怪問題。
可以這么說,Shadow DOM 旨在提供一種更好地組織頁面元素的方式,來為日趨復(fù)雜的頁面應(yīng)用提供強(qiáng)大支持,避免代碼間的相互影響。
看下在 chrome 它會是咋樣的:
我們可以通過 createShadowRoot() 來給一個元素節(jié)點(diǎn)創(chuàng)建 Shadow Root,這些元素類型必須是下邊列表的其中一個,否則會拋出 NotSupportedError 異常。
- 自定義的元素
- article
- aside
- blockquote
- body
- div
- header, footer
- h1, h2, h3, h4, h5, h6
- nav
- p
- section
- span
createShadowRoot() 是現(xiàn)在 chrome 實(shí)現(xiàn)的 API,來自文檔:https://www.w3.org/TR/2014/WD...。最新的文檔 API 已經(jīng)調(diào)整為 attachShadow()。
返回的 Shadow Root 對象從 DocumentFragment 繼承而來,所以可以使用相關(guān)的一些方法,例如shadowRoot.getElementById('id') 來獲取 Shadow DOM 里邊的元素。
簡單的使用如下:
- const div = document.getElementById('id')
- const shadowRoot = div.createShadowRoot()
- const span = document.createElement('span')
- span.textContent = 'hello world'
- shadowRoot.appendChild(span)
在這里,我把這個 div 成為是這個 Shadow DOM 的 宿主元素,下邊的內(nèi)容會延續(xù)使用這個稱呼。
Shadow DOM 本身就為了代碼隔離而生,所以在 document 上使用 query 時,是沒法獲取到 Shadow DOM 里邊的元素的,需要在 Shadow Root 上做 query 才行。
在這里附上一個文檔,里邊有詳細(xì)的關(guān)于新的標(biāo)準(zhǔn)和現(xiàn)在 blink 引擎實(shí)現(xiàn)的 Shadow DOM 的區(qū)別,官方上稱之為 v0 和 v1:Shadow DOM v1 in Blink。
API
Shadow Root 除了從 DocumentFragment 繼承而來的屬性和方法外,還多了另外兩個屬性:
- host 只讀屬性,用來獲取這個 Shadow Root 所屬的元素
- innerHTML 用來獲取或者設(shè)置里邊的 HTML 字符串,和我們常用的 element.innerHTML 是一樣的
另外,在最新的標(biāo)準(zhǔn)文檔中,元素除了上邊提到的 attachShadow 方法之外,還多了三個屬性:
- assignedSlot 只讀,這個元素如果被分配到了某個 Shadow DOM 里邊的 slot,那么會返回這個對應(yīng)的 slot 元素
- slot 元素的 slot 屬性,用來指定 slot 的名稱
- shadowRoot 只讀,元素下面對應(yīng)的 Shadow Root 對象
slot 是什么?接著看下邊的內(nèi)容,看完下一節(jié)的最后一部分就會明白上述內(nèi)容和 slot 相關(guān)的兩個 API 有什么作用。
slot
slot 提供了在使用自定義標(biāo)簽的時候可以傳遞子模板給到內(nèi)部使用的能力,可以簡單看下 Vue 的一個例子。
我們先來看下現(xiàn)在 chrome 可以跑的 v0 版本,這一個版本是提供了一個 content 標(biāo)簽,代表了一個占位符,并且有一個 select 屬性用來指定使用哪些子元素。
- <!-- component input-toggle template -->
- <input type="checkbox"></input>
- <content select=".span"></content>
自定義的元素里邊的子元素代碼是這樣的:
- <input-toggle name="hello">
- <span>hello</span>
- <span class="span">test</span>
- </input-toggle>
那么展現(xiàn)的結(jié)果會和下邊的代碼是一樣的:
- <input-toggle name="hello">
- <input type="checkbox"></input>
- <span class="span">test</span>
- </input-toggle>
這里只是說展現(xiàn)結(jié)果,實(shí)際上,input-toggle 里邊應(yīng)該是創(chuàng)建了一個 Shadow DOM,然后 content 標(biāo)簽引用了目標(biāo)的 span 內(nèi)容,在 chrome 看是這樣的:
然后,是最新標(biāo)準(zhǔn)中的 slot 使用方式,直接上例子代碼:
- <!-- component input-toggle template -->
- <input type="checkbox"></input>
- <slot name="text"></slot>
在自定義的元素標(biāo)簽是這么使用 slot 的:
- <input-toggle name="hello">
- <input type="checkbox"></input>
- <span class="span" slot="text">test</span>
- </input-toggle>
通過 slot="text" 的屬性來讓元素內(nèi)部的 slot 占位符可以引用到這個元素,多個元素使用這個屬性也是可以的。這樣子我們便擁有了使用標(biāo)簽是從外部傳 template 給到自定義元素的內(nèi)部去使用,而且具備指定放在那里的能力。
CSS 相關(guān)
因?yàn)橛?Shadow DOM 的存在,所以在 CSS 上又添加了很多相關(guān)的東西,其中一部分還是屬于討論中的草案,命名之類的可能會有變更,下邊提及的內(nèi)容主要來自文檔:Shadow DOM in CSS scoping 1,很多部分在 chrome 是已經(jīng)實(shí)現(xiàn)的了,有興趣可以寫 demo 試試。
因?yàn)?Shadow DOM 很大程度上是為了隔離樣式作用域而誕生的,主文檔中的樣式規(guī)則不對 Shadow DOM 里的子文檔生效,子文檔中的樣式規(guī)則也不影響外部文檔。
但不可避免的,在某些場景下,我們需要外部可以控制 Shadow DOM 中樣式,如提供一個組件給你,有時候你會希望可以自定義它內(nèi)部的一些樣式,同時,Shadow DOM 中的代碼有時候可能需要能夠控制其所屬元素的樣式,甚至,組件內(nèi)部可以定義上邊提到的通過 slot 傳遞進(jìn)來的 HTML 的樣式。所以呢,是的,CSS 選擇器中添加了幾個偽類,我們一一來看下它們有什么作用。
在閱讀下邊描述的時候,請留意一下選擇器的代碼是在什么位置的,Shadow DOM 內(nèi)部還是外部。
:host 用于在 Shadow DOM 內(nèi)部選擇到其宿主元素,當(dāng)它不是在 Shadow DOM 中使用時,便匹配不到任意元素。
在 Shadow DOM 中的 * 選擇器是無法選擇到其宿主元素的。
:host( <selector> ) 括號中是一個選擇器,這個可以理解為是一個用于兼容在主文檔和 Shadow DOM 中使用的方法,當(dāng)這個選擇器在 Shadow DOM 中時,會匹配到括號中選擇器對應(yīng)的宿主元素,如果不是,則匹配括號中選擇器能夠匹配到的元素。
文檔中提供了一個例子:
- <x-foo class="foo">
- <"shadow tree">
- <div class="foo">...</div>
- </>
- </x-foo>
在這個 shadow tree 內(nèi)部的樣式代碼中,會有這樣的結(jié)果:
- :host 匹配 <x-foo> 元素
- x-foo 匹配不到元素
- .foo 只匹配到 <div> 元素
- .foo:host 匹配不到元素
- :host(.foo) 匹配 <x-foo> 元素
:host-context( <selector> ),用于在 Shadow DOM 中來檢測宿主元素的父級元素,如果宿主元素或者其祖先元素能夠被括號中的選擇器匹配到的話,那么這個偽類選擇器便匹配到這個 Shadow DOM 的宿主元素。個人理解是用于在宿主元素外部元素滿足一定的條件時添加樣式。
::shadow 這個偽類用于在 Shadow DOM 外部匹配其內(nèi)部的元素,而 /deep/ 這個標(biāo)識也有同樣的作用,我們來看一個例子:
- <x-foo>
- <"shadow tree">
- <div>
- <span id="not-top">...</span>
- </div>
- <span id="top">...</span>
- </>
- </x-foo>
對于上述這一段代碼的 HTML 結(jié)構(gòu),在 Shadow DOM 外部的樣式代碼中,會是這樣的:
- x-foo::shadow > span 可以匹配到 #top 元素
- #top 匹配不到元素
- x-foo /deep/ span 可以匹配到 #not-top 和 #top 元素
/deep/ 這個標(biāo)識的作用和我們的 > 選擇器有點(diǎn)類似,只不過它是匹配其對應(yīng)的 Shadow DOM 內(nèi)部的,這個標(biāo)識可能還會變化,例如改成 >> 或者 >>> 之類的,個人感覺, >> 會更舒服。
最后一個,用于在 Shadow DOM 內(nèi)部調(diào)整 slot 的樣式,在我查閱的這個文檔中,暫時是以 chrome 實(shí)現(xiàn)的為準(zhǔn),使用 ::content 偽類,不排除有更新為 ::slot 的可能性。我們看一個例子來了解一下,就算名稱調(diào)整了也是差不多的用法:
- <x-foo>
- <div id="one" class="foo">...</div>
- <div id="two">...</div>
- <div id="three" class="foo">
- <div id="four">...</div>
- </div>
- <"shadow tree">
- <div id="five">...</div>
- <div id="six">...</div>
- <content select=".foo"></content>
- </"shadow tree">
- </x-foo>
在 Shadow DOM 內(nèi)部的樣式代碼中,::content div 可以匹配到 #one,#three 和 #four,留意一下 #two 為什么沒被匹配到,因?yàn)樗鼪]有被 content 元素選中,即不會進(jìn)行引用。如果更換成 slot 的 name 引用的方式亦是同理。
層疊規(guī)則,按照這個文檔的說法,對于兩個優(yōu)先級別一樣的 CSS 聲明,沒有帶 !important 的,在 Shadow DOM 外部聲明的優(yōu)先級高于在 Shadow DOM 內(nèi)部的,而帶有 !important 的,則相反。個人認(rèn)為,這是提供給外部一定的控制能力,同時讓內(nèi)部可以限制一定的影響范圍。
繼承方面相對簡單,在 Shadow DOM 內(nèi)部的頂級元素樣式從宿主元素繼承而來。
至此,Web Components 四個部分介紹結(jié)束了,其中有一些細(xì)節(jié),瀏覽器實(shí)現(xiàn)細(xì)節(jié),還有使用上的部分細(xì)節(jié),是沒有提及的,因?yàn)樵敿?xì)記錄的話,還會有很多東西,內(nèi)容很多。當(dāng)使用過程中有疑問時可以再次查閱標(biāo)準(zhǔn)文檔,有機(jī)會的話會再完善這個文章。下一部分會把這四個內(nèi)容組合起來,整體看下 Web Components 是怎么使用的。
Web Components
Web Components 總的來說是提供一整套完善的封裝機(jī)制來把 Web 組件化這個東西標(biāo)準(zhǔn)化,每個框架實(shí)現(xiàn)的組件都統(tǒng)一標(biāo)準(zhǔn)地進(jìn)行輸入輸出,這樣可以更好推動組件的復(fù)用。結(jié)合上邊各個部分的內(nèi)容,我們整合一起來看下應(yīng)該怎么使用這個標(biāo)準(zhǔn)來實(shí)現(xiàn)我們的組件:
- <!-- components/header.html -->
- <template id="">
- <style>
- ::content li {
- display: inline-block;
- padding: 20px 10px;
- }
- </style>
- <content select="ul"></content>
- </template>
- <script>
- (function() {
- const element = Object.create(HTMLInputElement.prototype)
- const template = document.currentScript.ownerDocument.querySelector('template')
- element.createdCallback = function() {
- const shadowRoot = this.createShadowRoot()
- const clone = document.importNode(template.content, true)
- shadowRoot.appendChild(clone)
- this.addEventListener('click', function(event) {
- console.log(event.target.textContent)
- })
- }
- document.registerElement('test-header', { prototype: element })
- })()
- </script>
這是一個簡單的組件的例子,用于定義一個 test-header,并且給傳遞進(jìn)來的子元素 li 添加了一些組件內(nèi)部的樣式,同時給組件綁定了一個點(diǎn)擊事件,來打印點(diǎn)擊目標(biāo)的文本內(nèi)容。
看下如何在一個 HTML 文件中引入并且使用一個組件:
- <!-- index.html -->
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <title></title>
- <link rel="import" href="components/header.html">
- </head>
- <body>
- <test-header>
- <ul>
- <li>Home</li>
- <li>About</li>
- </ul>
- </test-header>
- </body>
- </html>
一個 import 的 <link> 把組件的 HTML 文件引用進(jìn)來,這樣會執(zhí)行組件中的腳本,來注冊一個 test-header 元素,這樣子我們便可以在主文檔中使用這個元素的標(biāo)簽。
上邊的例子是可以在 chrome 正常運(yùn)行的。
所以,根據(jù)上邊簡單的例子可以看出,各個部分的內(nèi)容是有機(jī)結(jié)合在一起,Custom Elements 提供了自定義元素和標(biāo)簽的能力,template 提供組件模板,import 提供了在 HTML 中合理引入組件的方式,而 Shadow DOM 則處理組件間代碼隔離的問題。
不得不承認(rèn),Web Components 標(biāo)準(zhǔn)的提出解決了一些問題,必須交由瀏覽器去處理的是 Shadow DOM,在沒有 Shadow DOM 的瀏覽器上實(shí)現(xiàn)代碼隔離的方式多多少少有缺陷。個人我覺得組件化的各個 API 不夠簡潔易用,依舊有 getElementById 這些的味道,但是交由各個類庫去簡化也可以接受,而 import 功能上沒問題,但是加載多個組件時性能問題還是值得商榷,標(biāo)準(zhǔn)可能需要在這個方面提供更多給瀏覽器的指引,例如是否有可能提供一種單一請求加載多個組件 HTML 的方式等。
在現(xiàn)在的移動化趨勢中,Web Components 不僅僅是 Web 端的問題,越來越多的開發(fā)者期望以 Web 的方式去實(shí)現(xiàn)移動應(yīng)用,而多端復(fù)用的實(shí)現(xiàn)漸漸是以組件的形式鋪開,例如 React Native 和 Weex。所以 Web Components 的標(biāo)準(zhǔn)可能會影響到多端開發(fā) Web 化的一個模式和發(fā)展。
最后,再啰嗦一句,Web Components 個人覺得還是未來發(fā)展趨勢,所以才有了這個文章。