[譯] 組件化開發利器:Web Components標準

原文地址:hacks.mozilla.org/2018/11/the…javascript

原文做者:Potchcss

2018年11月15日發表於 Developer Tools, DOM, Featured Article, 以及 Web Componentshtml

譯者水平有限,若是有錯誤歡迎指正!前端

背景

自從第一個動態的 DHTML 光標拖拽的誕生,以及「本週網站」的徽章爲網站增色,可複用代碼對 web 開發者極具誘惑力。可是在本身的網站中引入三方 UI 組件一直是一個比較頭疼的事情。java

引入別人造好的輪子會帶來不少 javascript 和 css衝突,想一想那些可怕的 !important 吧。使用現代前端框架好比React可能會好一些,可是爲了爲了重用一些組件而引入一個框架顯然是有些笨重。 HTML5 把一些經常使用的組件引入 web 標準,像 <video><input type="date" />,可是,爲每一個經常使用Web UI庫添加新標準標籤並非一個可持續維護的方式。es6

這時,一些 web 標準草案就應運而生了。每一個標準有其獨立的功能,可是把他們組合在一塊兒,就能解決以前不能用原生方案解決的問題,而且它們很是難僞造,由於自定義 HTML 組件能夠像傳統 HTML 標籤同樣使用。這些組件把複雜的實現封裝在內部,就像富文本編輯器和視頻播放器同樣。web

標準發展

總體來講,這組標準就是 Web Components。在2018年前端組件化並非什麼新鮮事物。的確,從2014年開始,chrome一直以這樣或那樣的方式實現這些標準,其餘瀏覽器也有相應的polyfills。chrome

在標準委員會工做了一段時間以後,Web Components 標準從早期的形式(現稱爲version 0版本)演變成了更成熟的version 1版本,而且被主流瀏覽器實現。Firefox63 對此增長了兩個支持:Custom Elements、shadow DOM。如今一塊兒來看看怎麼扮演 HTML 的發明家吧!瀏覽器

Web Components已經存在了一段時間,相關資源比較多。本文只做爲初級讀物,介紹一些列的新性能和資源,若是你想了解更多(你也應該瞭解更多),請移步 MDN Web DocsGoogle Developers安全

自定義 HTML 標籤須要瀏覽器之前沒賦予開發者的新功能。我將在每一單元列出這些從前不能實現的地方,以及他們所使用的其餘 web 新技術。

<template> 標籤: 一個小複習

第一個標籤是老朋友了,它知足的需求早於 Web Components。有時你只想存儲一些 HTML。也許有時你要屢次複製標籤,也許有時你還不想立刻建立一個UI。<template> 標籤包含並解析 HTML ,但不把解析出的 DOM 添加到當前文檔中。

<template>
    <h1>This won't display!</h1> <script>alert("this won't alert!");</script> </template> 複製代碼

那麼解析出的 DOM 去哪了呢?它被添加到了「文檔碎片」中,能夠把它理解成一個包含 html 的薄容器。當被添加到 DOM 中時文檔碎片就解體了,當你想保留一組稍後使用的標籤,又不想保留其容器時,文檔碎片很是有用。

「那麼,我該怎麼使用一個正在解體的容器中的標籤呢?」

答案是:你只需把模版的文檔碎片插入當前文檔便可:

let template = document.querySelector('template');
document.body.appendChild(template.content);
複製代碼

上面這段代碼能夠正常執行,可是若是你剛解體了文檔碎片就會報錯!若是你重複運行上述代碼就會報錯。由於第二次運行時 template.content 已經沒有了。咱們應該用一個碎片的拷貝代替 template.content,而後再插入這個拷貝,代碼以下:

document.body.appendChild(template.content.cloneNode(true));
複製代碼

cloneNode 方法顧名思義,接收一個參數控制只拷貝標籤自己仍是包括它的子標籤。

新知識點:

  • <template> 標籤包含 HTML,可是不向當前文檔添加。

總結:

Custom Elements

Custom Elements是 Web Components標準的表明。它確實讓開發人員實現了自定義 HTML 標籤。這一切的實現得益於 ES6 的 class 語法糖。若是你對 javascript 或者其餘面嚮對象語言很熟悉的話,你能夠像這樣經過繼承來實現本身的類:

class MyClass extends BaseClass {
    // class definition goes here
}
複製代碼

咱們來試一下這樣寫:

class MyElement extends HTMLElement {}
複製代碼

不久以前這樣寫還會報錯。瀏覽器不容許原生 HTMLElement 類或其子類被繼承。Custom Elements 解除了這一限制。

瀏覽器會把 <p> 標籤映射到 HTMLParagraphElement 原生類,可是它怎麼映射自定義類呢?除了繼承內部類外,還有一個「自定義標籤註冊表」用於聲明這種映射:

customElements.define('my-element', MyElement);
複製代碼

如今頁面上的每一個 <my-element> 標籤都與一個 MyElement 元素對應。 頁面每解析一個 <my-element> 標籤就調用一次 MyElement 的構造函數。

爲何標籤名帶中橫線呢?標準制定者但願將來開發者能夠自由的自定義標籤,這意味着開發者均可以建立 <h7> 或者 <vr> 這樣的標籤。爲了不將來的衝突,全部自定義標籤必須加中橫線,同時原生 HTML 標籤保證毫不包含中橫線。問題解決!

除了標籤建立時會調用構造函數,還有一系列生命週期函數會在特定時刻被調用:

  • connectedCallback 當元素被添加到文檔中時調用。這個函數可能屢次調用,好比標籤移動、移除或從新添加時。
  • disconnectedCallbackconnectedCallback 相對應。
  • attributeChangeCallback 元素屬性更改時調用。

下面是一個稍複雜的例子:

class GreetingElement extends HTMLElement {
  constructor() {
    super();
    this._name = 'Stranger';
  }
  connectedCallback() {
    this.addEventListener('click', e => alert(`Hello, ${this._name}!`));
  }
  attributeChangedCallback(attrName, oldValue, newValue) {
    if (attrName === 'name') {
      if (newValue) {
        this._name = newValue;
      } else {
        this._name = 'Stranger';
      }
    }
  }
}
GreetingElement.observedAttributes = ['name'];
customElements.define('hey-there', GreetingElement);
複製代碼

在頁面上這樣使用:

<hey-there>Greeting</hey-there>
<hey-there name="Potch">Personalized Greeting</hey-there>
複製代碼

若是要繼承一個 HTML 原生標籤,你可能會想定義一個看起來徹底不一樣新標籤。好比讓 <hey-there> 去繼承 <button>

class GreetingElement extends HTMLButtonElement
複製代碼

同時要在自定義標籤註冊表中體現出繼承一個已有標籤:

customElements.define('hey-there', GreetingElement, { extends: 'button' });
複製代碼

咱們應該用被繼承的標籤加 is 屬性來表示這種繼承關係,而不是直接用自定義標籤,咱們這樣使用繼承 <button><hey-there> 標籤:

<button is="hey-there" name="World">Howdy</button>
複製代碼

這不是畫蛇添足,這樣程序就會知道 <hey-there> 是繼承的 <button>

這些對全部的傳統 web 標籤都適用。咱們可使用 <template> 設置一系列事件處理程序,添加自定義樣式,甚至能夠封裝一個內部結構。其餘人能夠經過 HTML 標籤、 DOM 調用、或者新框架(其中一些框架支持在虛擬 DOM 中自定義標籤名)的方式在本身的代碼中引用你的自定義組件。由於這些都是標準的 DOM 接口,因此 Custom Elements 實現了真正的可移植組件。

新知識點:

  • Custom Elements 能夠繼承原生 HTMLElement 類和其子類。
  • 經過 customElements.define() 維護自定義標籤註冊表。
  • 特定生命週期函數在標籤建立、添加到DOM、屬性被修改等時刻調用。

總結: ES6 Classes 特別是 子類和 extends 關鍵詞

Shadow DOM

咱們寫出了友好的 custom element,也爲其添加了漂亮的樣式。如今咱們想把它用在咱們的站點上,也想把代碼分享出去,讓更多的人用在他們的網站上。可是咱們怎麼避免自定義 <button> 標籤和其餘網站的 css 衝突?答案是使用 Shadow DOM。

Shadow DOM 標準提出了 shadow root 的概念。shadow root 有標準的 DOM 方法,也能夠像其餘 DOM 節點同樣添加到文檔中。shadow root 的亮點在於其內容不會出如今包含其父節點的文檔中:

// attachShadow creates a shadow root.
let shadow = div.attachShadow({ mode: 'open' });
let inner = document.createElement('b');
inner.appendChild(document.createTextNode('Hiding in the shadows'));
// shadow root supports the normal appendChild method.
shadow.appendChild(inner);
div.querySelector('b'); // empty
複製代碼

在上面的例子中,<div> 包含 <b> 而且 <b> 標籤也渲染在了頁面上,可是常規的 DOM 方法卻找不到它。不只如此,頁面的樣式也影響不到它。這意味着 shadow root 既不受外部樣式影響,其內部樣式也不會泄漏。但這邊界不涉及安全性,頁面上的 js 能夠檢測到 shadow root 的建立,經過 shadow root 的引用,能夠查詢到它裏面的內容。

爲 shadow root 裏的內容設置樣式能夠經過給根節點添加<style> (或者 <link>)標籤:

let style = document.createElement('style');
style.innerText = 'b { font-weight: bolder; color: red; }';
shadowRoot.appendChild(style);
let inner = document.createElement('b');
inner.innerHTML = "I'm bolder in the shadows";
shadowRoot.appendChild(inner);
複製代碼

如今咱們能夠真正使用 <template> 標籤了!無論用哪一種方法,shadow root 內部的 <b> 標籤樣式只會被根標籤上的樣式控制,不會受外部影響。

若是 custom element 不使用 shadow DOM 怎麼辦?咱們依然可使用一個新標籤 <slot>

<template>
  Hello, <slot></slot>!
</template>
複製代碼

若是這個模板被添加到一個 shadow root 中,那麼下述標籤:

<hey-there>World</hey-there>
複製代碼

將被渲染爲:

Hello, World!
複製代碼

這種將 shadow DOM 和非 shadow DOM 整合使用的功能,能夠把 custom element 複雜的實現封裝在其內部,而把調用變的簡單。slot的威力遠不止這些,還有多重 slot、命名 slot、針對特定內容的 css 僞類 slot 等。建議查閱文檔瞭解更多。

新知識點

  • 一種準屏蔽 DOM 結構 —— shadow root
  • 建立和訪問shadow root 的 DOM API
  • shadow root 的樣式做用域
  • 用於shadow root 和樣式做用域的新 css 僞類
  • <slot> 標籤

最終效果

最後來一塊兒實現這個漂亮的按鈕吧!咱們給這個按鈕取名 <fancy-button>。它的奇妙之處在於,它有定製的樣式,也容許咱們爲它添加圖標使它變得美觀。咱們把樣式封裝在 shadow root 中,這樣就能夠保證在任何引用它的網站上樣式保持不變。

你能夠查看下面這個完整的交互型代碼示例。請仔細查看 custom element 的 js 定義以及 <template> 標籤的樣式和結構。

點擊查看完整示例

總結

Web Components標準創建在這樣一種理念之上:提供多個底層功能,開發者以標準制定者不曾設想的方式把這些功能組合起來使用。Custom Elements 已經被用於在頁面上建立 VR 內容富UI工具等,並使這些變得簡單。儘管標準的敲定過程很漫長,Web Components 標準爲 Web 開發者提供了更多的可能。現代瀏覽器已經支持了這項技術,Web Components 的將來在你手中,使用它來創造奇蹟吧!

相關文章
相關標籤/搜索