天天看點

浏覽器原理 27 # WebComponent

說明

浏覽器工作原理與實踐專欄學習筆記

什麼是元件化?

特點:對内高内聚,對外低耦合。

對内各個元素彼此緊密結合、互相依賴,對外和其他元件的聯系最少且接口簡單。

阻礙前端元件化的因素

比如:在頁面中嵌入第三方内容時,需要確定第三方的内容樣式不會影響到目前内容,同樣也要確定目前的 DOM 不會影響到第三方的内容。

CSS 是如何阻礙前端元件化的

例子:比如不同人分别寫了不同的全局樣式,個人測試沒問題,但是合并時會出現 CSS 屬性影響到其他外部的标簽的樣式

<style>p {
      background-color: brown;
      color: cornsilk
   }</style>
<p>time.geekbang.org</p>      
<style>p {
      background-color: red;
      color: blue
   }</style>
<p>time.geekbang</p>      

顯然,CSS 的全局屬性會阻礙元件化。

DOM 也是阻礙元件化的一個因素,因為在頁面中任何地方都可以直接讀取和修改 DOM。

WebComponent 元件化開發

上面我們了解到了:CSS 和 DOM 是阻礙元件化的兩個因素,WebComponent 給出的解決思路

WebComponent 提供了對局部視圖封裝能力,可以讓 DOM、CSSOM 和 JavaScript 運作在局部環境中,這樣就使得局部的 CSS 和 DOM 不會影響到全局。

WebComponent 是怎麼實作元件化的

MDN:Web Components

Web Components 概念

Web Components旨在解決這些問題 — 它由三項主要技術組成,它們可以一起使用來建立封裝功能的定制元素,可以在你喜歡的任何地方重用,不必擔心代碼沖突。

  • Custom elements(自定義元素):一組JavaScript API,允許您定義custom elements及其行為,然後可以在您的使用者界面中按照需要使用它們。
  • Shadow DOM(影子DOM):一組JavaScript API,用于将封裝的“影子”DOM樹附加到元素(與主文檔DOM分開呈現)并控制其關聯的功能。通過這種方式,您可以保持元素的功能私有,這樣它們就可以被腳本化和樣式化,而不用擔心與文檔的其他部分發生沖突。
  • HTML templates(HTML模闆):​

    ​<template>​

    ​​ 和​

    ​<slot>​

    ​ 元素使您可以編寫不在呈現頁面中顯示的标記模闆。然後它們可以作為自定義元素結構的基礎被多次重用。

Web Components 使用

實作 web component 的基本方法通常如下所示:

  1. 建立一個類或函數來指定web元件的功能,如果使用類,請使用 ECMAScript 2015 的類文法。
  2. 使用​

    ​CustomElementRegistry.define()​

    ​ 方法注冊您的新自定義元素 ,并向其傳遞要定義的元素名稱、指定元素功能的類、以及可選的其所繼承自的元素。
  3. 如果需要的話,使用​

    ​Element.attachShadow()​

    ​ 方法将一個 shadow DOM 附加到自定義元素上。使用通常的 DOM 方法向 shadow DOM 中添加子元素、事件監聽器等等。
  4. 如果需要的話,使用​

    ​<template>​

    ​​ 和​

    ​<slot>​

    ​ 定義一個HTML模闆。再次使用正常DOM方法克隆模闆并将其附加到您的shadow DOM中。
  5. 在頁面任何您喜歡的位置使用自定義元素,就像使用正常HTML元素那樣。

Web Components 例子

要使用 WebComponent 需要實作下面三步:

1.使用 template 屬性來建立模闆。

利用 DOM 可以查找到模闆的内容,但是模闆元素是不會被渲染到頁面上的,在模闆的内部定義樣式資訊。
<!-- 定義模闆樣式 -->
<template id="kxm-template">
  <style>p {
      background-color: green;
      color: white;
    }

    div {
      width: 200px;
      background-color: blue;
      border: 3px solid red;
      border-radius: 10px;
    }</style>
  <div>
      <p>kxm</p>
      <p>kaimo313</p>
  </div>
  <script>function foo() {
      console.log('template log');
    }</script>
</template>      

2.建立一個類

  • 查找模闆内容;
  • 建立影子 DOM;
  • 再将模闆添加到影子 DOM 上。
可以把影子 DOM 看成是一個作用域,其内部的樣式和元素是不會影響到全局的樣式和元素的,而在全局環境下,要通路影子 DOM 内部的樣式或者元素也是需要通過約定好的接口的。
<!-- 建立一個類 -->
<script>class Kaimo313 extends HTMLElement {
    constructor() {
      super()
      // 擷取元件模闆
      const content = document.querySelector('#kxm-template').content;
      // 建立影子DOM節點
      const shadowDOM = this.attachShadow({ mode: 'open' });
      // 将模闆添加到影子DOM上
      shadowDOM.appendChild(content.cloneNode(true));
    }
  }
  // 使用 customElements.define 來自定義元素
  customElements.define('kaimo-313', Kaimo313);</script>      

3.使用該元素

<!-- 使用該元素 -->
<kaimo-313></kaimo-313>
<div>
  <p>kxm</p>
  <p>kaimo313</p>
</div>
<kaimo-313></kaimo-313>      

完整的代碼展示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>12.Web Components 例子</title>
</head>
<body>
  <h1>kxm 測試 Web Components 例子</h1>
  <!-- 定義模闆樣式 -->
  <template id="kxm-template">
    <style>p {
        background-color: green;
        color: white;
      }

      div {
        width: 200px;
        background-color: blue;
        border: 3px solid red;
        border-radius: 10px;
      }</style>
    <div>
        <p>kxm</p>
        <p>kaimo313</p>
    </div>
    <script>function foo() {
        console.log('template log');
      }</script>
  </template>
  <!-- 建立一個類 -->
  <script>class Kaimo313 extends HTMLElement {
      constructor() {
        super()
        // 擷取元件模闆
        const content = document.querySelector('#kxm-template').content;
        // 建立影子DOM節點
        const shadowDOM = this.attachShadow({ mode: 'open' });
        // 将模闆添加到影子DOM上
        shadowDOM.appendChild(content.cloneNode(true));
      }
    }
    // 使用 customElements.define 來自定義元素
    customElements.define('kaimo-313', Kaimo313);</script>
  <!-- 使用該元素 -->
  <kaimo-313></kaimo-313>
  <div>
    <p>kxm</p>
    <p>kaimo313</p>
  </div>
  <kaimo-313></kaimo-313>
</body>
</html>      

使用影子 DOM 的輸出效果

浏覽器原理 27 # WebComponent

浏覽器如何實作影子 DOM

影子 DOM 的作用

  1. 影子 DOM 中的元素對于整個網頁是不可見的;
  2. 影子 DOM 的 CSS 不會影響到整個網頁的 CSSOM,影子 DOM 内部的 CSS 隻對内部的元素起作用。

影子 DOM 示意圖

影子 DOM 都有一個 shadow root 的根節點

浏覽器原理 27 # WebComponent

浏覽器怎麼實作 DOM API 無法直接查詢到影子 DOM 的内部元素?

當通過 DOM 接口去查找元素時,渲染引擎會去判斷 kaimo-313 屬性下面的 shadow-root 元素是否是影子 DOM,如果是,那麼就直接跳過 shadow-root 元素的查詢操作。