原文鏈接 What are WebComponents and why are they important?
摘要
先看一看未來(lái)的 WebComponent 標準,再簡(jiǎn)單了解怎么寫(xiě) WebComponents,最后說(shuō)說(shuō)它的重要性。
這篇文章簡(jiǎn)單介紹 WebComponent 標準,介紹哪些瀏覽器已經(jīng)開(kāi)始支持 WebComponents,討論 WebComponents 能解決什么問(wèn)題,以及它對 web 開(kāi)發(fā)的重要性。你可以了解到如何利用 Vanilla javascript 編寫(xiě)一個(gè)簡(jiǎn)單的 WebComponent,我還會(huì )針對它的潛在優(yōu)勢分享我自己的一些拙見(jiàn)。
近年來(lái),web 開(kāi)發(fā)者們通過(guò)插件或者模塊的形式在網(wǎng)上分享自己的代碼,便于其他開(kāi)發(fā)者們復用這些優(yōu)秀的代碼。同樣的故事不斷發(fā)生,人們不斷的復用 javascript 文件,然后是 CSS 文件,當然還有 HTML 片段。但是你又必須祈禱這些引入的代碼不會(huì )摧毀你的網(wǎng)站或者web app。WebComponents 是這類(lèi)問(wèn)題最好的良藥,通過(guò)一種標準化的非侵入的方式封裝一個(gè)組件,每個(gè)組件能組織好它自身的 HTML 結構、CSS 樣式、javascript 代碼,并且不會(huì )干擾頁(yè)面上的其他代碼。
大家之前可能聽(tīng)說(shuō)過(guò) shadow DOM,但 shadow DOM 到底是什么? 開(kāi)發(fā)者能通過(guò) shadow DOM 在文檔流中創(chuàng )建一些完全獨立于其他元素的子 DOM 樹(shù)(sub-DOM trees), 由于這個(gè)特性,使得我們可以封裝一個(gè)具有獨立功能的組件,并且可以保證不會(huì )在不無(wú)意中干擾到其它 DOM 元素。shadow DOM 和標準的 DOM 一樣,可以設置它的樣式,也可以用 javascript 操作它的行為。主文檔流和基于 shadow DOM 創(chuàng )建的獨立組件之間的互不干擾,所以組件的復用也就變得異常簡(jiǎn)單方便。
只要你用過(guò)類(lèi)似 Angular JS 之類(lèi)的現代 javascript 框架,就一定對 HTML 模板再熟悉不過(guò)了。開(kāi)發(fā)者通過(guò)模板來(lái)復用一些 HTML 代碼段,在 HTML5 標準下我們甚至不需要 javascript 框架就能輕松使用模板。
在模板中創(chuàng )建 HTML 代碼塊和子 DOM 樹(shù),使得我們可以用不同的物理文件來(lái)組織代碼。通過(guò)<link>標簽來(lái)引入這些文件,就像我們在 PHP 文件中引用 javascript 文件那樣簡(jiǎn)單。
我們聲明一個(gè)語(yǔ)義化的自定義元素來(lái)引用組件,用 javascript 建立自定義元素和模板、shadow DOM 之間的關(guān)聯(lián),然后將自定義標簽(例如<my-custom-element></my-custom-element>)插入到頁(yè)面上就能得到一個(gè)封裝好的組件。Angular JS 中有很多類(lèi)似的寫(xiě)法。
現在你應該已經(jīng)對 WebComponents 有了一定的了解,但我想通過(guò)一個(gè)簡(jiǎn)單的例子能讓你更好的理解上面那些枯燥的概念。以下代碼展示了一個(gè)最簡(jiǎn)單的 WebComponent 由哪些元素組成,用<template>包裹 HTML 和 樣式代碼,用 javascript 將這些綁定到自定義標簽 <favorite-colour>上。
代碼laycode - v1.1<!-- WebComponent example based off element-boilerplate: https://github.com/webcomponents/element-boilerplate --><template> <style> .coloured { color: red; } </style> <p>My favorite colour is: <strong class="coloured">Red</strong></p></template><script> (function() { // Creates an object based in the HTML Element prototype var element = Object.create(HTMLElement.prototype); // Gets content from <template> var template = document.currentScript.ownerDocument.querySelector('template').content; // Fires when an instance of the element is created element.createdCallback = function() { // Creates the shadow root var shadowRoot = this.createShadowRoot(); // Adds a template clone into shadow root var clone = document.importNode(template, true); shadowRoot.appendChild(clone); }; document.registerElement('favorite-colour', { prototype: element }); }());</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
用<link />引入 WebComponent 文件,并添加<favorite-colour>標簽將 WebComponent 添加到頁(yè)面上,如下代碼所示:
代碼laycode - v1.1<!DOCTYPE html><html><head lang="en"> <meta charset="UTF-8"> <title>My First WebComponent</title> <link rel="import" href="components/favorite-colour.html" /></head><body> <favorite-colour></favorite-colour></body></html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
這個(gè)簡(jiǎn)單的例子演示了如何創(chuàng )建一個(gè)易復用、可維護的 WebComponent。
你可能會(huì )說(shuō):“確實(shí)很diao,但尼瑪什么時(shí)候才能 '真正' 用得上呢?”。我會(huì )告訴你:“現在就能用了少年”。下面的表格列出了主流瀏覽器對 WebComponent 中各個(gè)特性的兼容性。
雖然大部分瀏覽器還不支持 WebComponent ,但是有個(gè)叫做 webcomponentsjs 的兼容庫,可以讓 WebComponent 在不支持它的瀏覽器上運行起來(lái)。只要你在項目中引入這個(gè)庫,就可以像上面的例子那樣將 WebComponents 用起來(lái)。
WebComponents 將如何改變當前的 Web 開(kāi)發(fā)模式?本文的開(kāi)頭已經(jīng)給出了答案,“通過(guò)一種標準化的非侵入的方式封裝一個(gè)組件”,但這究竟能帶來(lái)哪些好處呢?
本文的前面已經(jīng)介紹了開(kāi)發(fā)者可以通過(guò) shadow DOM 創(chuàng )建子 DOM 樹(shù),并且不會(huì )被頁(yè)面上的 CSS 樣式和 javascript 腳本所影響。顯而易見(jiàn)的好處就是當你引入一個(gè)第三方組件的時(shí)候,不用擔心它會(huì )對你的網(wǎng)站其他功能造成影響。對于開(kāi)發(fā)者來(lái)說(shuō),開(kāi)發(fā)無(wú)害插件變得更簡(jiǎn)單了。下面的例子用剛才寫(xiě)的 WebComponent 展示了這種封裝的獨立性。在 WebComponent 內部定義了一個(gè)名為colour的類(lèi),并將color屬性設置為 red 。在主頁(yè)面中colour類(lèi)的color為 green 并被設為!important,你會(huì )發(fā)現在 WebComponent 中的顏色還是展示為紅色。 你可以訪(fǎng)問(wèn) Github 獲取示例代碼。
標準的目的是增強通用性。一旦 WebComponents 被廣泛支持起來(lái),我們就能開(kāi)發(fā)更通用的組件,而不用考慮其他項目用的是什么技術(shù)。再也不用針對 jQuery 寫(xiě)插件,再也不用為 Angular JS 寫(xiě) directives,再也不用為 Ember.js 寫(xiě) addons。 一勞永逸,是 WebComponents 帶來(lái)的最大好處。作為一個(gè)全職的 Angular JS 開(kāi)發(fā)者,經(jīng)常需要將 jQuery 插件翻譯成 directives,然后才能在我的項目里用起來(lái),這些工作非常繁瑣。程序員不應該局限于某一種前端框架,但現實(shí)情況是我們正在被一個(gè)個(gè)前端框架所限制,因為不同框架的代碼不能共享。WebComponents 能將我們從水深火熱之中解救出來(lái)。
通過(guò)這樣的標準編寫(xiě)的組件具有更好的可維護性。最佳實(shí)踐能夠更快的被采用,并給我們帶來(lái)更快更可靠的 Web 應用。測試會(huì )變得更簡(jiǎn)單,測試規范也能隨著(zhù)組件一起發(fā)布。
我們可以在 WebComponents 里開(kāi)發(fā)復雜的功能,就可以將較少的精力耗費在開(kāi)發(fā)復雜 Web 應用上了。你只需要將這些組件組裝起來(lái),保證他們之間能夠互相通信,就能組裝出一個(gè)完整應用。以 Angular JS 1.x 為例,不需要寫(xiě) controllers,不需要寫(xiě) directives,不需要寫(xiě)那么多的 scope,只要提供一些基本的服務(wù)和路由就行。當然 Angular 2.0 已經(jīng)將 WebComponents 規劃進(jìn)去了。
HTML 5 規范帶來(lái)了一些新的語(yǔ)義化標簽,例如<section>,nav。這以為著(zhù)不用詳細閱讀代碼細節就能了解開(kāi)發(fā)者的意圖。WebComponents 將徹底改變我們使用 HTML 的方式,在組件的 HTML 代碼層面,自定義元素和屬性能表達更多語(yǔ)義。如下面的例子所示:
代碼laycode - v1.1<!-- PAGE NAVIGATION --><div> <ul> <li>Home</li> <li>About</li> <li>Contact</li> </ul></div><!-- CONTENT AREA --><div> <p>Here is some simple content in the content area.</p></div><!-- GALLERY --><div> <img src="animage1.png" /> <img src="animage2.png" /> <img src="animage3.png" /> <img src="animage4.png" /> <img src="animage5.png" /></div><!-- FOOTER --><div> <p>A simple footer</p></div>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
代碼laycode - v1.1<page-navigation data-position="top"></page-navigation><content data-theme="dark"> <p>Here is some simple content in the content area.</p></content><image-gallery data-fullscreen="true"> <img src="animage1.png" /> <img src="animage2.png" /> <img src="animage3.png" /> <img src="animage4.png" /> <img src="animage5.png" /></image-gallery><footer> <p>A simple footer</p></footer>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
雖然上述例子比較簡(jiǎn)單,我們還是能從中看出兩者的顯著(zhù)區別。第一個(gè)例子使用標準的 HTML 標簽,很難直接從代碼看出最終的渲染結果,而第二個(gè)例子使用了 HTML5 標簽和自定義標簽,從代碼層面提供了更多有用信息。從這些具有語(yǔ)義的標簽就能很快理解頁(yè)面每一個(gè)區塊的含義,例如page-navigation和footer。其它信息可以通過(guò)自定義屬性傳遞,例如data-fullscreen和data-position這樣的屬性就很好的描述了它將傳遞給頁(yè)面什么數據。
上述的那些優(yōu)點(diǎn)能讓 Web 開(kāi)發(fā)變得更美好,希望你跟我一樣激動(dòng)并滿(mǎn)懷期待。但是...也有可能帶來(lái)一些問(wèn)題,歷史一次次證明 Web 標準的實(shí)際應用可能會(huì )分裂為多個(gè)分支,給我們帶來(lái)艱難的抉擇,我擔心這樣的事情也會(huì )發(fā)生在 WebComponents 身上。
代碼laycode - v1.1<!-- Import X-Tag --><script src="../bower_components/x-tag-core/src/core.js"></script><template> <p>Hello <strong></strong> :)</p></template><script> (function(window, document, undefined) { // Refers to the "importer", which is index.html var thatDoc = document; // Refers to the "importee", which is src/hello-world.html var thisDoc = document._currentScript.ownerDocument; // Gets content from <template> var template = thisDoc.querySelector('template').content; xtag.register('hello-world', { lifecycle: { created: function() { // Caches <strong> DOM query this.strong = template.querySelector('strong'); // Creates the shadow root this.shadowRoot = this.createShadowRoot(); this.uiSetWho(); }, attributeChanged: function() { this.uiSetWho(); } }, accessors: { who: { attribute: {}, get: function(){ return this.getAttribute('who') || "World" }, set: function(value){ this.xtag.data.who = value; } } }, methods: { uiSetWho: function() { // Sets "who" value into <strong> this.strong.textContent = this.who; // Removes shadow root content this.shadowRoot.innerHTML = ''; // Adds a template clone into shadow root var clone = thatDoc.importNode(template, true); this.shadowRoot.appendChild(clone); } } }); })(window, document);</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
代碼laycode - v1.1<!-- Import Polymer --><link rel="import" href="../bower_components/polymer/polymer.html"><!-- Define your custom element --><polymer-element name="hello-world" attributes="who"> <template> <p>Hello <strong>{{who}}</strong> :)</p> </template> <script> Polymer('hello-world', { who: 'World' }); </script></polymer-element>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
等等,看起來(lái)不對勁??!我承認我還沒(méi)有真正這兩個(gè)框架開(kāi)發(fā)過(guò),有沒(méi)有人能告訴我是在杞人憂(yōu)天,但這看起來(lái)就真的是在用兩種完全不兼容的方式開(kāi)發(fā) WebComponents。
我對 Angular 2 也有同樣的擔心,他們聲稱(chēng)完全支持 WebComponent 標準,但顯然還會(huì )有很多框架層面的東西。Angular 團隊應該很清楚該怎么做并且為什么這么做,希望利大于弊吧。如果 Angular 團隊成員能看到這篇文章,并向大家介紹 WebComponents 在 Angular 2 中的用法,那就再好不過(guò)了。
前面提到了語(yǔ)義化 HTML 標簽的好處,我們通過(guò)閱讀代碼就能快速理解它的含義。當然這還取決于開(kāi)發(fā)者是否使用語(yǔ)義化標簽和語(yǔ)義化的自定義屬性做開(kāi)發(fā)。更重要的是,社區應盡快提供優(yōu)秀的最佳實(shí)踐來(lái)引導普通開(kāi)發(fā)者形成更好的習慣。
WebComponents 能徹底改變 Web 開(kāi)發(fā),但還需時(shí)日。前端社區需要做大量工作才能使 WebComponents 變得真正可用,才能讓大家享受到組件式 Web 開(kāi)發(fā)的便利。
你可以在 WebComponents.org 這個(gè)網(wǎng)站了解更多關(guān)于 WebComponents 的知識。他們的 GitHub 賬號 里有很多適合學(xué)習的例子,本文的例子也來(lái)自其中。
我會(huì )很樂(lè )意聽(tīng)到你們對本文的評論和對 WebComponent 的見(jiàn)解。
聯(lián)系客服