Web Components 簡述

要說最近幾年來,前端開發最火的一個趨勢或最火的前端開發框架是什麼,第一想到的是,組件及推崇組件化開發的React框架。本文將介紹Web Components規範並就組件的幾大特性進行討論。javascript

前言

在開啓本篇的閱讀以前,先問一個問題,組件是什麼?html

組件,是數據和方法的一個封裝,其定義了一個可重用的軟件元素的功能,展現和使用,一般表現爲一個或一組可重用的元素。前端

組件的特性是什麼?一般能夠總結爲如下幾點:java

  1. 可拓展性:既然組件是針對某一特定功能或需求開發的,那它就必須易於開發和拓展;
  2. 封裝性:組件做爲一個獨立總體供使用,應該是對內修改,對外封閉,只供使用,而不對使用環境產生反作用;
  3. 易用性:組件的目的是產生可重用的獨立部件,那就必須提供一種簡單快捷的方式供使用。

組件化,給前端開發帶來了極大的效率提高,是近幾年以來web開發發展的趨勢,各類組件化的用戶界面庫,框架也層出不窮,如,React,Vue,Ionic等,這些框架關於組件化都有各自的實現,推崇理念,與編程規範,各大框架的支持者之間的爭論也是向來不斷,而若想在不一樣框架間切換,成本仍是挺高的,由於畢竟誰都但願本身能佔主流,佔據絕對優點地位,就像當前IE與網景瀏覽器之爭,延續到如今,各種瀏覽器標準兼容差別萬千,近年來w3c不斷在爲web標準規範作努力,Web Components就是推出的關於組件化的一個標準,但願它能將組件化更好的帶進web開發,同時儘可能保證標準規範,開發者能夠更好的關注於開發,而不是框架選擇與爭論之上。git

Web components特徵

Web Components將一系列特性加入HTML和DOM規範,使得開發者能夠自由建立在web應用或文檔可重用的元素或部件,其由四部分組成:github

  • 自定義元素(Custom Elements):定義新HTML元素的一系列API;
  • 影子DOM(Shadow DOM):組合對DOM和樣式的封裝;
  • HTML導入(HTML Imports):定義在文檔中導入其餘HTML文檔的方式;
  • HTML模板(HTML Templates):HTML內的DOM模板,在<template>元素內聲明。

自定義元素(Custom Elements)

自定義元素支持開發者定義一類新HTML元素,聲明其行爲和樣式,自定義元素分兩類:web

  • 自定義標籤元素(Autonomous custom elements):徹底獨立於原始HTML元素標籤的新標籤元素,其全部行爲須要開發者定義;
  • 自定義內置元素(Customized built-in):基於HTML原始元素標籤的自定義元素,以便於使用原始元素的特性,開發者只須要定義拓展行爲;

支持建立自定義元素,Web Components比較好的實現了組件開發的可拓展性。ajax

建立自定義標籤元素

爲了建立一個自定義標籤元素,咱們須要繼承HTMLELement類, 如在不少頁面咱們常常會有一鍵回到頁面頂部功能,咱們建立一個返回頂部的組件:編程

class GoTop extends HTMLElement {
        constructor() {
            super();
          }
    }
    customElements.define('go-top', GoTop);複製代碼

在須要使用該組件的頁面只需像使用正常HTML元素同樣:瀏覽器

<go-top>Top</go-top>複製代碼

固然,該元素的一切樣式,行爲,事件監聽,默認行爲均須要開發者自行定義,沒法期待它有像<button>同樣的默認行爲詳細參考建立自定義標籤元素

建立自定義內置元素

不少時候咱們並不須要徹底建立一個新元素,而只是須要在某些內置元素基礎上進行拓展,建立自定義內置元素,須要繼承該類元素類,如HTMLButtonElementHTMLDivElement

class MenuButton extends HTMLButtonElement {
        ...
    }
    customElements.define('menu-button', MenuButton);複製代碼

使用也很簡單,和內置元素同樣的語法;不一樣的是,在須要使用自定義內置元素時,爲內置元素添加is特性,該特性值對應建立的自定義內置元素名稱:

<button is="menu-button">menu</button>複製代碼

該元素默認行爲繼承自<button>元素,可是咱們能夠爲其設置拓展功能或性質。

對比

經過上面實例可知,自定義標籤元素與內置元素主要表如今兩點不一樣:

  • 標籤:自定義標籤元素是徹底獨立的一個新元素,新標籤,而自定義內置標籤,使用的依然是已有內置標籤;
  • 行爲與樣式:自定義內置元素,繼承內置元素的默認行爲,樣式及語義,能夠進行拓展,而自定義標籤元素,徹底須要開發者定義相關聲明。

影子DOM(Shadow DOM)

DOM,即文檔對象模型,是HTML文檔的一個結構表示,以樹形結構表示一個文檔,文檔中元素間關係按照父子,兄弟關係排列;DOM規範提供一系列API支持咱們操做文檔節點,即一般所說的DOM API。

前面提到Web Components指封裝DOM和樣式,以組件的形式在文檔中使用,而不一樣於JavaScript中函數會造成一個單獨做用域,文檔DOM樹的層次結構中是不存在局部做用域概念的,也就是說文檔內全部定義的樣式都對整個文檔產生影響,文檔中的樣式也會影響組件內的聲明樣式,而不限定於元素所處位置,這樣顯然極大阻礙了組件的獨立性和可重用性,是必需要解決的問題,不過不用擔憂,這都已經解決了,解決方案就是下文介紹的attachShadow()方法。

影子DOM API提供了attachShadow()方法,建立一個影子DOM,支持將封裝的內容或組件做爲一個獨立DOM子樹附加進一個HTML文檔,組件內與外部隔離,樣式互不影響,這也印證了組件開發的封裝性需求。

建立

要建立一個影子DOM,很簡單,使用attachShadow()方法便可,而須要注意的是全部影子DOM必須和一個文檔中存在的元素(HTML內置元素或自定義元素)綁定,才能使用:

var frag = document.createElement('div');
    var shadowRoot = frag.attachShadow({mode: 'open'});
    shadowRoot.innerHTML = '<p>Shadow DOM Content</p>';複製代碼

影子樹(shadow tree)與影子主體(shadow host)

上文使用attachShadow()方法建立的元素就是一個影子DOM,而其子內容就構成一棵影子樹(shadow tree),而和影子DOM綁定,也就是包含該樹的文檔內元素一般稱爲影子主體(shadow host)。

槽位(slot)

如上,當一個元素(即影子主體)內存在影子DOM,瀏覽器默認只會渲染該影子DOM的影子樹,而不渲染影子主體的其餘子內容,如,現有某元素<div class="menus">,在文檔中使用以下:

<div class="menus">
        <h2>Menus</h2>
    </div>複製代碼

給該元素綁定影子DOM:

var menus = document.querySelector('.menus');
    var shadowRoot = menus.attachShadow({mode: 'open'});
    shadowRoot.innerHTML = '<ul>\ <li>Home</li>\ <li>About</li>\ </ul>';複製代碼

其影子樹內容爲:

<ul>
        <li>Home</li>
        <li>About</li>
    </ul>複製代碼

最後渲染結果以下:

<div class="menus">
        <ul> <li>Home</li> <li>About</li> </ul>
    </div>複製代碼

你好發現影子主體本來的子元素內容沒有被渲染,那麼是否是沒辦法了?固然不是,若是要保存子內容,須要使用<slot>槽位元素,至關於作一個佔位符,只須要把前文影子主體內容修改成以下:

<div class="menus">
        <h2>Menus</h2>
        <slot></slot>
    </div>複製代碼

渲染結果以下, 一切符合需求:

<div class="menus">
        <h2>Menus</h2>
        <ul>
            <li>Home</li>
            <li>About</li>
        </ul>
    </div>複製代碼
命名槽(named slots)

上文顯示的是隻有一個槽位的實例,假如須要有多個分組怎麼辦呢?Web Components也有解決方案,那就是使用命名槽,即給槽位添加name屬性,依然使用如上實例,修改影子主體內容:

<div class="menus">
        <slot></slot>
        <slot name="top"></slot>
        <slot name="right"></slot>
    </div>複製代碼

假如影子樹內容以下:

<h2>Menus</h2>
    <ul slot="top">
        <li>Home</li>
        <li>About</li>
    </ul>
    <ul slot="right">
        <li>Home</li>
        <li>Top</li>
    </ul>複製代碼

渲染結果以下:

<div class="menus">
        <h2>Menus</h2>
        <ul>
            <li>Home</li>
            <li>About</li>
        </ul>
        <ul>
        <li>Home</li>
        <li>Top</li>
    </ul>
    </div>複製代碼

如上,能夠發現擁有name屬性的槽位由對應slot屬性值相同的影子子樹替換,而剩下的內容默認替換空名槽位,若不存在空名槽位,則剩餘內容將被拋棄。

樣式

前文已經提到,Web Components定義的組件內的樣式與外部環境的樣式是互不影響的,那麼如何爲組件設置樣式呢,依然使用<style>標籤:

<head>
        <style>
            .top {margin-top: 30px;}
        </style>
    </head>
    ...

    <div class="top">
        ...
    </div>
    ...

    <div class="menus">
        #shadow-root
        <style>
            .top {margin-top: 10px;}
        </style>
        <div class="top">
            ...
        </div>
    </div>複製代碼

如上實例,在組件內部top類元素margin-top值爲10px,而外部top類元素margin-top值爲30px,二者是獨立的。

渲染

關於影子DOM樹的渲染,其方式與web文檔DOM樹的渲染方式並沒有區別,均由瀏覽器渲染引擎進行渲染,須要注意的是,影子樹的DOM渲染過程和文檔DOM樹的渲染是獨立分別進行的。

HTML引入(HTML Imports)

如何在HTML文檔中引入另外一個web文檔或web組件呢?像JSP或PHP語言都對HTML語法進行了拓展,咱們可使用諸如<include>標籤直接引入另外一個文檔,然而在這以前,原生HTML規範並不支持直接引入另外一文檔,一般都得經過ajax請求另外一文檔內容,而後經過JavaScript使用DOM API將內容插入,對於組件化開發和使用,這樣顯然不是咱們指望的結果,這與組件的易用性是背離的,因此,HTML imports定義瞭如何在文檔內引入和重用另外一文檔。

在文檔內直接引入外鏈資源的文檔或web組件,語法以下,使用<link>標籤:

<link rel="import" href="components.html">複製代碼

假如在components.html中定義了got-top自定義元素,則在本文檔內能夠直接使用:

<go-top>GoTop</go-top>複製代碼

如上,僅僅將<link>標籤的rel屬性設置成import便可,另外值得注意的是:爲了不重複執行引入文檔內的腳本,對於已加載文檔,import方式將跳過其加載和執行過程。

HTML模板(HTML Templates)

爲了更友好的處理組件模板,Web Components規範,支持<template>模板標籤,HTML模板定義了使用<template>標籤聲明能夠經過腳本操做插入文檔的HTML模板片斷:

<template id="menusTemplate">
        <ul> <li>Home</li> <li>About</li> </ul>
    </template>複製代碼

使用腳本操做,該元素content屬性可訪問模板內容:

var menusTemplate = document.querySelector('#menusTemplate');
    var frag = document.importNode(menusTemplate.content, true);
    document.querySelector('.menus').appendChild(frag);複製代碼

template標籤

<template>標籤本質上與其餘HTML內置標籤同樣,可使用DOM API進行操做,可是須要明白,在將模板激活(生成DOM或插入文檔)前:

  1. <template>標籤內的內容不會被渲染;
  2. 標籤內的圖片,等媒體資源不會被加載;
  3. 標籤不會出如今DOM樹,審查元素看不到;

關於兼容性

對於Web Components規範的兼容性,目前仍是須要使用webcomponentsjs polyfills的方式支持開發,總的來講,目前Safari 10, Google Chrome (53)兼容的更好;雖然兼容性並很差,還在推動過程當中,可是對其進行學習仍是頗有必要的。

類庫

推薦幾個常見的Web組件類庫:

  • Polymer: Google推出的web組件庫,支持數據的單向和雙向綁定,兼容性較好,跨瀏覽器性能也較好;
  • X-Tag: 微軟推出的開源庫,支持Web Components規範,兼容Web ComponentsAPI;
  • Slim.js:輕量級的web組件庫,專一於幫助開發者更好的編寫原生web組件,而不依賴於其餘框架,可是也提供了良好的拓展性,開發者能夠自由拓展。

參考

  1. Component
  2. Web Components Specifications
  3. Web Components WiKi
  4. Web Components MDN

歡迎訪問個人我的博客 - 熊建剛的博客

相關文章
相關標籤/搜索