了解 Web Components

Web Components

首先來了解下 Web Components 的基本概念, Web Component 是指一系列加入 w3c 的 HTML與DOM的特性,目的是為了從原生層面實現(xiàn)組件化,可以使開發(fā)者開發(fā)、復(fù)用、擴展自定義組件沐兵,實現(xiàn)自定義標(biāo)簽。

這是目前前端開發(fā)的一次重大的突破便监。它意味著我們前端開發(fā)人員開發(fā)組件時扎谎,不必關(guān)心那些其他MV框架的兼容性,真正可以做到 “Write once, run anywhere”烧董。*

例如:

// 假如我已經(jīng)構(gòu)建好一個 Web Components 組件 <hello-world>并導(dǎo)出
// 在 html 頁面毁靶,我們就可以直接引用組件
<script src="/my-component.js"></script>

// 而在 html 里面我們可以這樣使用
<hello-world></hello-word>

而且跟任何框架無關(guān),代表著它不需要任何外部 runtime 的支持逊移,也不需要復(fù)雜的Vnode算法映射到實際DOM预吆,只是瀏覽器api本身對標(biāo)簽內(nèi)部邏輯進行一些編譯處理,性能必定會比一些MV*框架要好一些胳泉。

那它是怎么做到高性能的呢拐叉?主要和它的核心API有關(guān)。其實在上篇中我們已經(jīng)簡單提到了 Web Components 的三個核心 API扇商,接下來我?guī)Т蠹以敿?xì)分析各個api所承擔(dān)的功能和實際用法凤瘦,想必了解過 Web Component 核心技術(shù)后,大家就不會對它感到陌生了钳吟。

三個核心API

Custom elements(自定義元素)

首先來了解下自定義元素廷粒,其實它是作為 Web Component 的基石窘拯。那么我們來看下這個基石提供了哪些方法红且,提供給我們進行高樓大廈的建設(shè)坝茎。

  1. 自定義元素掛載方法

自定義元素通過CustomElementRegistry 來自定義可以直接渲染的html元素,掛載在 window.customElements.define 來供開發(fā)者調(diào)用暇番,demo 如下:

// 假如我已經(jīng)構(gòu)建好一個 Web Components 組件 <hello-world>并導(dǎo)出

class HelloWorld extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: 'open' });
        this.shadowRoot.innerHTML = `
           <style>
                :host {
                    display: block;
                    padding: 10px;
                    background-color: #eee;
                }
            </style>
            <h1>Hello World!</h1>
        `;
    }
}

// 掛載
window.customElements.define('hello-world', HelloWorld)

// 然后就可以在 html 中使用
<hello-world></hello-world>

注意:自定義元素必須用'-'連接符連接嗤放,來作為特定的區(qū)分,如果沒有檢測到自定義元素壁酬,則瀏覽器會作為空div處理次酌。

渲染結(jié)果:

  1. 自定義元素的類

由上面的例子 "class HelloWorld extends HTMLElement { xxx } " 發(fā)現(xiàn),自定義元素的構(gòu)造都是基于 HTMLElement舆乔,所以它繼承了 HTML 元素特性岳服,當(dāng)然,也可以繼承 HTMLElement的派生類希俩,如:HTMLButtonElement 等吊宋,來作為現(xiàn)有標(biāo)簽的擴展。

  1. 自定義元素的生命周期

類似于現(xiàn)有MV*框架的生命周期颜武,自定義元素的基類里面也包含了完整的生命周期 hook 來提供給開發(fā)者實現(xiàn)一些業(yè)務(wù)邏輯的應(yīng)用:

class HelloWorld extends HTMLElement {
    constructor() {
        // 1 構(gòu)建組件的時候的邏輯 hook
        super();
    }
  // 2 當(dāng)自定義元素首次被渲染到文檔時候調(diào)用 
  connectedCallback(){
  } 
  // 3 當(dāng)自定義元素在文檔中被移除調(diào)用 
  disconnectedCallback(){ 
  } 
  // 4 當(dāng)自定義組件被移動到新的文檔時調(diào)用
  adoptedCallback(){ 
  } 
  // 5 當(dāng)自定義元素的屬性更改時調(diào)用
  attributeChangedCallback(){  
  }
}
  1. 添加自定義方法和屬性

由于自定義元素由一個類來構(gòu)造璃搜,所以添加自定義屬性和方法就如同平常開發(fā)類的方法一致。

class HelloWorld extends HTMLElement {
    constructor() {
        super();
    }

    tag = 'hello-world'

    say(something: string) {
        console.log(`hello world, I want to say ${this.tag} ${something}`)
    }
}

// 調(diào)用方法如下
const hw = document.querySelector('hello-world'); 
hw.say('good'); 

// 控制臺打印效果如下

Shadow DOM(影子DOM)

有了自定義元素作為基石鳞上,我們想要更加順暢的進行組件化封裝这吻,必定少不了對于DOM樹的操作。那么好的篙议,Shadow DOM(影子DOM)就應(yīng)運而生了唾糯。

顧名思義,影子DOM就是用來隔離自定義元素不受到外界樣式或者一些副作用的影響涡上,或者內(nèi)部的一些特性不會影響外部趾断。使自定義元素保持一個相對獨立的狀態(tài)。

在我們?nèi)粘i_發(fā)html頁面的時候也會接觸到一些使用 Shadow DOM 的標(biāo)簽吩愧,比如:audio 和 video 等芋酌;在具體dom樹中它會一一個標(biāo)簽存在,會隱藏內(nèi)部的結(jié)構(gòu)雁佳,但是其中的控件脐帝,比如:進度條、聲音控制等糖权,都會以一個Shadow DOM存在于標(biāo)簽內(nèi)部堵腹,如果想要查看具體的DOM結(jié)構(gòu),則可以嘗試在chrome的控制臺 -> Preferences -> Show user agent Shadow DOM星澳, 就可以查看到內(nèi)部的結(jié)構(gòu)構(gòu)成疚顷。

如果組件使用Shadow host,常規(guī)document中會存在一個 Shadow host節(jié)點用來掛載 Shadow DOM,Shadow DOM內(nèi)部也會存在一個DOM樹:Shadow Tree腿堤,根節(jié)點為Shadow root阀坏,外部可以用偽類:host來訪問,Shadow boundary其實就是Shadow DOM的邊界笆檀。具體架構(gòu)圖如下:

下面我們通過一個簡單的例子來看下Shadow DOM的實際用處:

// Shadow DOM 開啟方式為

this.attachShadow( { mode: 'open' } );
  • 不使用Shadow DOM
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Web Components</title>
    <style> h1 {
            font-size: 20px;
            color: yellow;
        } </style>
  </head>

  <body>
    <div></div>
    <hello-world></hello-world>
    <h1>Hello World! 外部</h1>
    <script type="module"> class HelloWorld extends HTMLElement {
            constructor() {
                super();
                // 關(guān)閉 shadow DOM
                // this.attachShadow({ mode: 'open' });

                const d = document.createElement('div');
                const s = document.createElement('style');
                s.innerHTML = `h1 {
                            display: block;
                            padding: 10px;
                            background-color: #eee;
                        }`
                d.innerHTML = `
                    <h1>Hello World! 自定義組件內(nèi)部</h1>
                `;

                this.appendChild(s);
                this.appendChild(d);
            }

            tag = 'hello-world'

            say(something) {
                console.log(`hello world, I want to say ${this.tag} ${something}`)
            }
        }

        window.customElements.define('hello-world', HelloWorld);
        const hw = document.querySelector('hello-world'); 
        hw.say('good'); </script>
  </body>
</html>

渲染效果為忌堂,可以看到樣式已經(jīng)互相污染:

  • 使用 Shadow DOM
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Web Components</title>
    <style> h1 {
            font-size: 20px;
            color: yellow;
        } </style>
  </head>
  <body>
    <div></div>
    <hello-world></hello-world>
    <h1>Hello World! 外部</h1>
    <script type="module"> class HelloWorld extends HTMLElement {
            constructor() {
                super();
                this.attachShadow({ mode: 'open' });
                this.shadowRoot.innerHTML = `
                    <style>
                        h1 {
                            font-size: 30px;
                            display: block;
                            padding: 10px;
                            background-color: #eee;
                        }
                    </style>
                    <h1>Hello World! 自定義組件內(nèi)部</h1>
                `;
            }

            tag = 'hello-world'

            say(something) {
                console.log(`hello world, I want to say ${this.tag} ${something}`)
            }
        }

        window.customElements.define('hello-world', HelloWorld);
        const hw = document.querySelector('hello-world'); 
        hw.say('good'); </script>
  </body>
</html>

渲染結(jié)果為:

可以清晰的看到樣式直接互相隔離無污染,這就是Shadow DOM的好處酗洒。

HTML templates(HTML模板)

template模板可以說是大家比較熟悉的一個標(biāo)簽了士修,在Vue項目中的單頁面組件中我們經(jīng)常會用到,但是它也是 Web Components API 提供的一個標(biāo)簽樱衷,它的特性就是包裹在 template 中的 HTML 片段不會在頁面加載的時候解析渲染棋嘲,但是可以被 js 訪問到,進行一些插入顯示等操作矩桂。所以它作為自定義組件的核心內(nèi)容封字,用來承載 HTML 模板,是不可或缺的一部分耍鬓。

使用場景如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Web Components</title>
    <style> h1 {
            font-size: 20px;
            color: yellow;
        } </style>
</head>

<body>
    <div></div>
    <hello-world></hello-world>

    <template id="hw"> 
    <style> .box { 
        padding: 20px;
    } 

    .box > .first { 
        font-size: 24px; 
        color: red;
    } 

    .box > .second { 
        font-size: 14px; 
        color: #000;
    } </style> 

    <div class="box"> 
        <p class="first">Hello</p> 
        <p class="second">World</p> 
    </div> 
    </template>

    <script type="module"> class HelloWorld extends HTMLElement { 
            constructor() {
                super(); 
                const root = this.attachShadow({ mode: 'open' });
               root.appendChild(document.getElementById('hw').content.cloneNode(true));
            }
        } 
        window.customElements.define('hello-world', HelloWorld); </script>
</body>

</html>

渲染結(jié)果為:

Slot 大家應(yīng)該也比較熟悉了阔籽,相當(dāng)于一個連接組件內(nèi)部和外部的一個占位機制,可以用來傳遞 HTML 代碼片段牲蜀,這里我就不過多贅述笆制,有需要繼續(xù)了解的同學(xué),Google一下即可涣达。

說完了 Web Components 的“三駕馬車”在辆,大家一定對于 Web Components 有了深入的了解,也熟悉了 Web Components 一些常規(guī)寫法度苔。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末匆篓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子寇窑,更是在濱河造成了極大的恐慌鸦概,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件甩骏,死亡現(xiàn)場離奇詭異窗市,居然都是意外死亡,警方通過查閱死者的電腦和手機饮笛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門咨察,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人福青,你說我怎么就攤上這事摄狱∨Ч睿” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵媒役,是天一觀的道長誉券。 經(jīng)常有香客問我,道長刊愚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任踩验,我火速辦了婚禮鸥诽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘箕憾。我一直安慰自己牡借,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布袭异。 她就那樣靜靜地躺著钠龙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪御铃。 梳的紋絲不亂的頭發(fā)上碴里,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機與錄音上真,去河邊找鬼咬腋。 笑死,一個胖子當(dāng)著我的面吹牛睡互,可吹牛的內(nèi)容都是我干的根竿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼就珠,長吁一口氣:“原來是場噩夢啊……” “哼寇壳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起妻怎,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤壳炎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后逼侦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冕广,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年偿洁,在試婚紗的時候發(fā)現(xiàn)自己被綠了撒汉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡涕滋,死狀恐怖睬辐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤溯饵,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布侵俗,位于F島的核電站,受9級特大地震影響丰刊,放射性物質(zhì)發(fā)生泄漏隘谣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一啄巧、第九天 我趴在偏房一處隱蔽的房頂上張望寻歧。 院中可真熱鬧,春花似錦秩仆、人聲如沸码泛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽噪珊。三九已至,卻和暖如春齐莲,著一層夾襖步出監(jiān)牢的瞬間痢站,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工选酗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瑟押,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓星掰,卻偏偏與公主長得像多望,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子氢烘,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 簡介 Web Components 標(biāo)準(zhǔn)非常重要的一個特性是怀偷,它使開發(fā)者能夠?qū)TML頁面的功能封裝為 custo...
    DoNow閱讀 442評論 0 0
  • 組件的概念 組件,是數(shù)據(jù)和方法的一個封裝播玖,其定義了一個可重用的軟件元素的功能椎工,展示和使用,通常表現(xiàn)為一個或一組可重...
    zx_lau閱讀 2,441評論 0 3
  • 前言:這周完成了兩場技術(shù)分享會蜀踏,下周還有一場维蒙,就完成了這階段的一個重大任務(wù)。分享會是關(guān)于 TS 的果覆,我這兩場分享會...
    CondorHero閱讀 991評論 0 2
  • 前言 不知不覺颅痊,2019年即將接近尾聲,現(xiàn)有前端三大框架也各自建立著自己的生態(tài)局待、自己的使用群體斑响。從angular1...
    Kaku_fe閱讀 2,778評論 0 19
  • 前端組件化其實是個持續(xù)了很長時間的過程了菱属,從最開始的jquery的插件開始。我們就在封裝一些js舰罚,css纽门,html...
    潘逸飛閱讀 9,809評論 1 16