了解 Web Components 對我們的重要性

 這篇文章簡單介紹 Web Component 標準,介紹哪些瀏覽器已經開始支持 Web Components,討論 Web Components 能解決什麼問題,以及它對 Web 開發的重要性。你可以瞭解到如何利用 Vanilla Javascript 編寫一個簡單的 Web Component,我還會針對它的潛在優勢分享我自己的一些拙見。

 

本文来自@meikidd发布在前端乱炖。如需转载,烦请注明原文出处:http://www.html-js.com/article/2779。英文原文:What are WebComponents and why are they important?

 

 

什麼是 Web Components ?

近年來,Web 開發者們通過插件或者模塊的形式在網上分享自己的代碼,便於其他開發者們復用這些優秀的代碼。同樣的故事不斷發生,人們不斷的復用 JavaScript 文件,然後是 CSS 文件,當然還有 HTML 片段。但是你又必須祈禱這些引入的代碼不會摧毀你的網站或者Web App。Web Components 是這類問題最好的良藥,通過一種標準化的非侵入的方式封裝一個組件,每個組件能組織好它自身的 HTML 結構、CSS 樣式、JavaScript 代碼,並且不會干擾頁面上的其他代碼。

The Shadow DOM

大家之前可能聽說過 shadow DOM,但 shadow DOM 到底是什麼? 開發者能通過 shadow DOM 在文檔流中創建一些完全獨立於其他元素的子 DOM 樹(sub-DOM trees), 由於這個特性,使得我們可以封裝一個具有獨立功能的組件,並且可以保證不會在不無意中干擾到其它 DOM 元素。shadow DOM 和標準的 DOM 一樣,可以設置它的樣式,也可以用 JavaScript 操作它的行為。主文檔流和基於 shadow DOM 創建的獨立組件之間的互不干擾,所以組件的復用也就變得異常簡單方便。

HTML 模板

只要你用過類似 Angular JS 之類的現代 JavaScript 框架,就一定對 HTML 模板再熟悉不過了。開發者通過模板來復用一些 HTML 代碼段,在 HTML5 標準下我們甚至不需要 JavaScript 框架就能輕鬆使用模板。

導入 HTML 模板

在模板中創建 HTML 代碼塊和子 DOM 樹,使得我們可以用不同的物理文件來組織代碼。通過<link>標籤來引入這些文件,就像我們在 PHP 文件中引用 JavaScript 文件那樣簡單。

自定義元素

我們聲明一個語義化的自定義元素來引用組件,用 JavaScript 建立自定義元素和模板、shadow DOM 之間的關聯,然後將自定義標籤(例如<my-custom-element></my-custom-element>)插入到頁面上就能得到一個封裝好的組件。Angular JS 中有很多類似的寫法。

試寫第一個 Web Component

現在你應該已經對 WebComponents 有了一定的瞭解,但我想通過一個簡單的例子能讓你更好的理解上面那些枯燥的概念。以下代碼展示了一個最簡單的 WebComponent 由哪些元素組成,用<template>包裹 HTML 和 樣式代碼,用 JavaScript 將這些綁定到自定義標籤 <favorite-colour>上。

<!-- 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>

<link />引入 WebComponent 文件,並添加<favorite-colour>標籤將 WebComponent 添加到頁面上,如下代碼所示:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>My First WebComponent</title>
    <link rel="import" href="/blog/components/favorite-colour.html" />
</head>
<body>
    <favorite-colour></favorite-colour>
</body>
</html>

個簡單的例子演示了如何創建一個易復用、可維護的 WebComponent。

WebComponent 兼容性

你可能會說:“確實很diao,但尼瑪什麼時候才能 '真正' 用得上呢?”。我會告訴你:“現在就能用了少年”。下面的表格列出了主流瀏覽器對 WebComponent 中各個特性的兼容性。意料之中的是基於 Blink 的瀏覽器在這方面處於領先地位。上表清晰的告訴我們,哪怕是最簡單的 WebComponent 例子也必須用 Chrome 或 Opera 運行。

意料之中的是基於 Blink 的瀏覽器在這方面處於領先地位。上表清晰的告訴我們,哪怕是最簡單的 WebComponent 例子也必須用 Chrome 或 Opera 運行。

填補空缺

雖然大部分瀏覽器還不支持 WebComponent ,但是有個叫做 webcomponentsjs 的兼容庫,可以讓 WebComponent 在不支持它的瀏覽器上運行起來。只要你在項目中引入這個庫,就可以像上面的例子那樣將 WebComponents 用起來。

WebComponents 的重要性

WebComponents 將如何改變當前的 Web 開發模式?本文的開頭已經給出了答案,“通過一種標準化的非侵入的方式封裝一個組件”,但這究竟能帶來哪些好處呢?

無害插件

本文的前面已經介紹了開發者可以通過 shadow DOM 創建子 DOM 樹,並且不會被頁面上的 CSS 樣式和 JavaScript 腳本所影響。顯而易見的好處就是當你引入一個第三方組件的時候,不用擔心它會對你的網站其他功能造成影響。對於開發者來說,開發無害插件變得更簡單了。下面的例子用剛才寫的 WebComponent 展示了這種封裝的獨立性。在 WebComponent 內部定義了一個名為colour的類,並將color屬性設置為 red 。在主頁面中colour類的color為 green 並被設為!important,你會發現在 WebComponent 中的顏色還是展示為紅色。 你可以訪問 Github 獲取示例代碼。

 

 

一勞永逸

標準的目的是增強通用性。一旦 WebComponents 被廣泛支持起來,我們就能開發更通用的組件,而不用考慮其他項目用的是什麼技術。再也不用針對 jQuery 寫插件,再也不用為 Angular JS 寫 directives,再也不用為 Ember.js 寫 addons。 一勞永逸,是 WebComponents 帶來的最大好處。作為一個全職的 Angular JS 開發者,經常需要將 jQuery 插件翻譯成 directives,然後才能在我的項目裡用起來,這些工作非常繁瑣。程序員不應該侷限於某一種前端框架,但現實情況是我們正在被一個個前端框架所限制,因為不同框架的代碼不能共享。WebComponents 能將我們從水深火熱之中解救出來。

維護與測試

通過這樣的標準編寫的組件具有更好的可維護性。最佳實踐能夠更快的被採用,並給我們帶來更快更可靠的 Web 應用。測試會變得更簡單,測試規範也能隨著組件一起發佈。

Abstraction

我們可以在 WebComponents 裡開發複雜的功能,就可以將較少的精力耗費在開發複雜 Web 應用上了。你只需要將這些組件組裝起來,保證他們之間能夠互相通信,就能組裝出一個完整應用。以 Angular JS 1.x 為例,不需要寫 controllers,不需要寫 directives,不需要寫那麼多的 scope,只要提供一些基本的服務和路由就行。當然 Angular 2.0 已經將 WebComponents 規劃進去了。

HTML 的故事

HTML 5 規範帶來了一些新的語義化標籤,例如<section>,nav。這以為著不用詳細閱讀代碼細節就能瞭解開發者的意圖。WebComponents 將徹底改變我們使用 HTML 的方式,在組件的 HTML 代碼層面,自定義元素和屬性能表達更多語義。如下面的例子所示:

傳統的 HTML 寫法

<!-- 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="/blog/animage1.png" />
    <img src="/blog/animage2.png" />
    <img src="/blog/animage3.png" />
    <img src="/blog/animage4.png" />
    <img src="/blog/animage5.png" />
</div>
<!-- FOOTER -->
<div>
    <p>A simple footer</p>
</div>

 

WebComponents 的語義化寫法.

<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="/blog/animage1.png" />
    <img src="/blog/animage2.png" />
    <img src="/blog/animage3.png" />
    <img src="/blog/animage4.png" />
    <img src="/blog/animage5.png" />
</image-gallery>
<footer>
    <p>A simple footer</p>
</footer>

 

雖然上述例子比較簡單,我們還是能從中看出兩者的顯著區別。第一個例子使用標準的 HTML 標籤,很難直接從代碼看出最終的渲染結果,而第二個例子使用了 HTML5 標籤和自定義標籤,從代碼層面提供了更多有用信息。從這些具有語義的標籤就能很快理解頁面每一個區塊的含義,例如page-navigationfooter。其它信息可以通過自定義屬性傳遞,例如data-fullscreendata-position這樣的屬性就很好的描述了它將傳遞給頁面什麼數據。

另一個大坑

上述的那些優點能讓 Web 開發變得更美好,希望你跟我一樣激動並滿懷期待。但是...也有可能帶來一些問題,歷史一次次證明 Web 標準的實際應用可能會分裂為多個分支,給我們帶來艱難的抉擇,我擔心這樣的事情也會發生在 WebComponents 身上。目前已經有三個基於 WebComponent 標準的框架,並且都很好的兼容低級瀏覽器。這本來是件好事,但也意味著我們開發 WebComponents 應用時有三個選擇:X-TagPolymerBosonic。既然都是支持 WebComponent 標準,那麼基於 Polymer 開發的組件代碼能夠用在 x-tag 組件裡嗎?反過來呢?看看下面的例子。

X-Tag 組件 (源碼)

<!-- Import X-Tag -->
<script src="/blog/../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>

 

Polymer 組件 (源碼)

<!-- Import Polymer -->
<link rel="import" href="/blog/../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>

等等,看起來不對勁啊!我承認我還沒有真正這兩個框架開發過,有沒有人能告訴我是在杞人憂天,但這看起來就真的是在用兩種完全不兼容的方式開發 WebComponents。

我對 Angular 2 也有同樣的擔心,他們聲稱完全支持 WebComponent 標準,但顯然還會有很多框架層面的東西。Angular 團隊應該很清楚該怎麼做並且為什麼這麼做,希望利大於弊吧。如果 Angular 團隊成員能看到這篇文章,並向大家介紹 WebComponents 在 Angular 2 中的用法,那就再好不過了。

前面提到了語義化 HTML 標籤的好處,我們通過閱讀代碼就能快速理解它的含義。當然這還取決於開發者是否使用語義化標籤和語義化的自定義屬性做開發。更重要的是,社區應盡快提供優秀的最佳實踐來引導普通開發者形成更好的習慣。

總結

WebComponents 能徹底改變 Web 開發,但還需時日。前端社區需要做大量工作才能使 WebComponents 變得真正可用,才能讓大家享受到組件式 Web 開發的便利。

你可以在 WebComponents.org 這個網站瞭解更多關於 WebComponents 的知識。他們的 GitHub 賬號 裡有很多適合學習的例子,本文的例子也來自其中。

我會很樂意聽到你們對本文的評論和對 WebComponent 的見解。

 

本文来自@meikidd发布在前端乱炖。如需转载,烦请注明原文出处:http://www.html-js.com/article/2779。英文原文:What are WebComponents and why are they important?