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

老沙322發表於2019-03-02

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

原文作者:Potch

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

譯者水平有限,如果有錯誤歡迎指正!

背景

自從第一個動態的 DHTML 游標拖拽的誕生,以及“本週網站”的徽章為網站增色,可複用程式碼對 web 開發者極具誘惑力。但是在自己的網站中引入三方 UI 元件一直是一個比較頭疼的事情。

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

這時,一些 web 標準草案就應運而生了。每個標準有其獨立的功能,但是把他們組合在一起,就能解決之前不能用原生方案解決的問題,並且它們非常難偽造,因為自定義 HTML 元件可以像傳統 HTML 標籤一樣使用。這些元件把複雜的實現封裝在內部,就像富文字編輯器和視訊播放器一樣。

標準發展

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

在標準委員會工作了一段時間之後,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 的未來在你手中,使用它來創造奇蹟吧!

相關文章