Web Components技術(shù)初探

image.png

前言

不知不覺牲芋,2019年即將接近尾聲苹支,現(xiàn)有前端三大框架也各自建立著自己的生態(tài)膝蜈、自己的使用群體。從angular1.0跨時代的開創(chuàng)了前端MVVM模型(在其他平臺已經(jīng)存在的模型级历,如WPF),到React組件化設(shè)計思路的誕生叭披,到Vue借鑒兩位前輩的思路寥殖,創(chuàng)造屬于自己的技術(shù)體系。

隨著各大框架版本的更迭涩蜘,組件化的思路因?yàn)榇蟠筇岣吡碎_發(fā)效率依舊一直是各大框架的核心(angular從angular2開始)嚼贡,從未改變。其實(shí)同诫,早在react誕生之前粤策,組件化這個概念,已經(jīng)在2011年前端開發(fā)者大會上被提出并完成納入w3c標(biāo)準(zhǔn)误窖。到現(xiàn)在叮盘,基本主流的瀏覽器都對他進(jìn)行了兼容秩贰。本文便是對這一技術(shù)的初探,大家寫膩了三大框架柔吼,不妨看看原生的組件要怎么玩

image.png

WebComponent中的三個概念

在WebComponents技術(shù)體系中萍膛,主要由以下三項技術(shù)所組成,通過組合這三項技術(shù),可以創(chuàng)建屬于自己功能的組件嚷堡。

  1. Custom elements(自定義元素) 用于定義自定義標(biāo)簽蝗罗。
  2. Shadow DOM(影子DOM) 類似于沙盒,將dom結(jié)構(gòu)附加到元素上蝌戒,保證功能或者樣式的私有串塑,而不用擔(dān)心污染其他功能或者樣式。
  3. HTML templates(HTML模板) 可以當(dāng)做缺少了數(shù)據(jù)綁定的vuetemplate標(biāo)簽北苟,主要承擔(dān)了組件結(jié)點(diǎn)渲染桩匪,也提供了slot插入內(nèi)容。

基于以上的內(nèi)容簡介友鼻,我們來看看這三項技術(shù)具體要怎么使用

Custom elements

Cumtom elements 這個概念對于寫慣了三大框架的開發(fā)者而言非常的用于理解傻昙,自定義標(biāo)簽,我們在其他框架經(jīng)常通過組件的形式彩扔,使用自己定義的標(biāo)簽妆档,就拿vue來舉例,我們在vue中會見到下面這樣的代碼虫碉。

<template>
  <x-toast>測試</x-toast>
</template>
<script>
  import Toast from 'Toast';
  export default {
    components:{
      'x-toast': Toast
    }
  // ...省略其他代碼
  }
</script>

在這里贾惦,"x-toast"就是一個自定義標(biāo)簽,用于定義自己的功能敦捧,對于Web Component须板,我們可以使用CustomElementRegistry.define方法來自定義元素,該方法接受三個參數(shù)

  1. 表示所創(chuàng)建的元素名稱的符合DOMString 標(biāo)準(zhǔn)的字符串兢卵。注意习瑰,custom element 的名稱不能是單個單詞,且其中必須要有短橫線秽荤。
  2. 用于定義元素行為的
  3. 一個包含 extends屬性的配置對象甜奄,是可選參數(shù)。它指定了所創(chuàng)建的元素繼承自哪個內(nèi)置元素王滤,可以繼承任何內(nèi)置元素贺嫂。

基于以上的定義,我們可以這樣定義一個這樣的標(biāo)簽雁乡。

CustomElementRegistry.define('todo-list', TodoList);

針對第二個參數(shù)TodoList,我們參照參數(shù)描述第喳,主要用于定義元素行為的,他擁有兩種類型踱稍,通過繼承來確定類型方式

  1. **Autonomous custom elements , 獨(dú)立元素曲饱,即html中可以直接使定義的標(biāo)簽悠抹,需要繼承 ** HTMLElement
class TodoList extend HTMLElement {
  construct(){
    super();
  }
}
CustomElementRegistry.define('todo-list', TodoList);

在html中我們就可以直接這么使用

  <todo-list><todo-list/>
  1. Customized built-in elements 繼承自基本元素,并不像獨(dú)立元素一樣扩淀,他依賴于div,p等基本元素標(biāo)簽楔敌,通過繼承對應(yīng)的標(biāo)簽,來拓展其功能驻谆,具體使用的時候卵凑,通過is屬性來區(qū)分原生標(biāo)簽。
class TodoList extend HTMLParagraphElement {
  construct(){
    super();
  }
}
CustomElementRegistry.define('todo-list', TodoList, {extends: 'p'});

在html中需要配合is屬性使用

<p is="todo-list">

在這里我們提到了如何定義一個元素(組件)胜臊,對應(yīng)vue/react組件勺卢,WebComponents也有屬于自己的生命周期鉤子函數(shù),當(dāng)我們定義一個元素時象对,他會在元素的不同階段觸發(fā)他們黑忱。

  1. connectedCallback:當(dāng)元素首次被插入文檔DOM時,被調(diào)用勒魔。
  2. disconnectedCallback:當(dāng)元素從文檔DOM中刪除時甫煞,被調(diào)用。
  3. adoptedCallback:當(dāng)元素被移動到新的文檔時冠绢,被調(diào)用抚吠。
  4. attributeChangedCallback: 當(dāng)元素增加、刪除唐全、修改自身屬性時埃跷,被調(diào)用蕊玷。

在這4個鉤子函數(shù)中 1邮利、2、4非常好理解垃帅,我們都可以從其他框架找到對應(yīng)延届,第3可能就比較難于理解,什么叫移動到新的文檔時被調(diào)用贸诚,咱們通過一個例子來說明

function createWindow(srcdoc) {
  let p = new Promise(resolve => {
    let f = document.createElement('iframe');
    f.srcdoc = srcdoc || '';
    f.onload = e => {
      resolve(f.contentWindow);
    };
    document.body.appendChild(f);
  });
  return p;
}

// 1. 創(chuàng)建2個Iframe w1,和w2
Promise.all([createWindow(), createWindow()])
  .then(([w1, w2]) => {
    // 2. 在w1這個iframe中創(chuàng)建了一個自定義元素'x-adopt'
    w1.customElements.define('x-adopt', class extends w1.HTMLElement {
      adoptedCallback() {
        console.log('Adopted!');
      }
    });
    // 3. 實(shí)例化這個自定義元素
    let a = w1.document.createElement('x-adopt');

    // 4. 將這個自定義元素插入w2這個iframe中
    w2.document.body.appendChild(a);
  });

上面這個例子方庭,便是移動到新的文檔中,我在iframe1中創(chuàng)建了一個屬于iframe1的新的元素酱固,但是卻將他插入iframe2,這樣就是將的其插入其他文檔械念,因此會觸發(fā)adoptedCallback生命周期鉤子。

注意在WebComponents的attributeChangedCallback运悲,這個生命周期鉤子之中龄减,我們要通過定義observedAttributes這個靜態(tài)方法,約定你要監(jiān)聽的屬性班眯,才會觸發(fā)attributeChangedCallback回調(diào)希停,如下所示

  class CustomInput extends Base{

  // 定義監(jiān)聽屬性
  static get observedAttributes() {
    return ['value'];
  }

  // 當(dāng)自定義元素的一個屬性被增加烁巫、移除或更改時被調(diào)用。:
  attributeChangedCallback(name, oldValue, newValue) {
  }
}

customElements.define('custom-input', CustomInput);

如上面代碼所示宠能,當(dāng)我改變了custom-inputvalue屬性時亚隙,才會觸發(fā)attributeChangedCallback,回調(diào)违崇,如果你改變了name或者其他非value屬性的時候阿弃,便不會觸發(fā)(第一次寫Web Components時可能需要注意,第一次本人就怎么也沒有辦法觸發(fā)這個回調(diào))

Shadow DOM

Shadow DOM其實(shí)并不是一個新的概念羞延,很早之前恤浪,Chrome就可以通過控制臺Setting顯示頁面的Shadow DOM

image.png

把這一項勾選后,你在通過控制臺Elements看看元素肴楷,你會發(fā)現(xiàn)有一些原生的標(biāo)簽水由,也有屬于自己的Shadow DOM, 如截圖中的input元素。
image.png

#shadow-root稱為起始根節(jié)點(diǎn) 赛蔫,在圖中可以看到砂客,他是寄宿在input標(biāo)簽之上,當(dāng)然這是一個最簡單的Shadow DOM,其實(shí) Shadow DOM也和普通元素一樣呵恢,可以嵌套使用鞠值,可以在一個#shadow-root中嵌入別的#shadow-root。如原生標(biāo)簽video就是如此渗钉,大家可以自己打開控制臺看看彤恶。

image.png

介紹了這么多Shadow DOM的知識,他的主要功能如上面介紹的鳄橘,他主要保證功能或者樣式的私有声离,而不用擔(dān)心污染其他功能或者樣式

那么我們來看看他是怎么和Web Components結(jié)合使用

對HTML元素而言瘫怜,他的實(shí)例中有一個方法 attachShadow,他會返回shadowRoot并掛載到這個元素實(shí)例上术徊,因此我們只需要調(diào)用他,便可以生成Shadow DOM鲸湃;

class TodoList extend HTMLElement {
  construct(){
    super();
    const shadowRoot = this.attachShadow({ mode: 'open' });
  }
}
CustomElementRegistry.define('todo-list', TodoList);

我們看見赠涮,在調(diào)用方法時傳入了一個對象,對象中有這樣的屬性{ mode: 'open' }暗挑,當(dāng)mode傳入open時笋除,你可以通過元素實(shí)體this.shadowRoot獲取shadowDOM節(jié)點(diǎn),當(dāng)傳入close時炸裆,便沒法這樣獲取垃它,如video標(biāo)簽,你無法通過this.shadowRoot獲取到,學(xué)過JAVA等面向?qū)ο蟮耐瑢W(xué)應(yīng)該會發(fā)現(xiàn),這是不是和將屬性定義為private以及public很像呢嗤瞎?

template

template是這三個概念之中最簡單的了墙歪,即使用<template>標(biāo)簽,來完成shadowRoot結(jié)點(diǎn)渲染贝奇,因?yàn)?code><template>標(biāo)簽并不會渲染到html元素上虹菲,因此我們可以利用這一特性來復(fù)用template。如以下的代碼

<template id="my-paragraph">
  <style>
    :host{
    }
    p {
      color: white;
      background-color: #666;
      padding: 5px;
    }
  </style>
  <p>My paragraph</p>
</template>
customElements.define('my-paragraph',
  class extends HTMLElement {
    constructor() {
      super();
      let template = document.getElementById('my-paragraph');
      let templateContent = template.content;
      const shadowRoot = this.attachShadow({mode: 'open'})
        .appendChild(templateContent.cloneNode(true));
  }
})

我們通過getElementById獲取到模板掉瞳,然后拿到他的內(nèi)容毕源,通過拿到的shadowRoot元素appendChild到結(jié)點(diǎn)中去,是不是非常的簡單陕习?

我們注意到模板中有一段style寫了一個偽類 :host霎褐,這個偽類主要是給其宿主元素添加樣式。我們可以使用類似這樣的選擇器该镣,控制不同class下冻璃,結(jié)點(diǎn)的樣式。
如以下樣式只在自定義元素存在test這個類才會生效损合,如<x-test class="test"></x-foo>

:host(.test:host) {
  ...
}

一點(diǎn)優(yōu)化

相對于傳統(tǒng)的template標(biāo)簽省艳,接觸webpack后我更喜歡通過模塊化的方式引入,對webpack而言嫁审,一切皆是模塊跋炕,我們只需要寫html文件,通過對應(yīng)loader進(jìn)來后即可律适,如:

// template.html
<style>
    :host{
    }
    p {
      color: white;
      background-color: #666;
      padding: 5px;
    }
  </style>
  <p>My paragraph</p>
import template from 'template.html';
customElements.define('my-paragraph',
  class extends HTMLElement {
    constructor() {
      super();
      let template = document.getElementById('my-paragraph');
      let templateContent = template.content;

      const shadowRoot = this.attachShadow({mode: 'open'});
      shadowRoot.innerHTML = template辐烂; // 通過Import 是不是更方便了呢?
  }
})

總結(jié)

Web Components作為原生的組件化方案捂贿,沒有數(shù)據(jù)綁定寫起來還是挺麻煩的纠修,不過,對于一些小工具而言眷蜓,天然不需要任何依賴分瘾,項目純凈,寫起來也是不錯的吁系,最近我也在使用Web Component寫一個chrome插件,用于YAPI Mock攔截白魂,也歡迎大家體驗(yàn)使用汽纤。
https://github.com/JackyTianer/yapi-mock-chrome-plugin

寫文不易,如果覺得文章有用福荸,動動手點(diǎn)個贊吧蕴坪,您的贊是我創(chuàng)作的最大動力!謝謝

參考文章

維基百科_WebComponent
Web開發(fā)技術(shù) WebComponents
自定義元素

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市背传,隨后出現(xiàn)的幾起案子呆瞻,更是在濱河造成了極大的恐慌,老刑警劉巖径玖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件痴脾,死亡現(xiàn)場離奇詭異,居然都是意外死亡梳星,警方通過查閱死者的電腦和手機(jī)赞赖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冤灾,“玉大人前域,你說我怎么就攤上這事≡隙郑” “怎么了匿垄?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長归粉。 經(jīng)常有香客問我年堆,道長,這世上最難降的妖魔是什么盏浇? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任变丧,我火速辦了婚禮,結(jié)果婚禮上绢掰,老公的妹妹穿的比我還像新娘痒蓬。我一直安慰自己,他們只是感情好滴劲,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布攻晒。 她就那樣靜靜地躺著,像睡著了一般班挖。 火紅的嫁衣襯著肌膚如雪鲁捏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天萧芙,我揣著相機(jī)與錄音给梅,去河邊找鬼。 笑死双揪,一個胖子當(dāng)著我的面吹牛动羽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播渔期,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼运吓,長吁一口氣:“原來是場噩夢啊……” “哼渴邦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拘哨,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤谋梭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后倦青,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瓮床,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年姨夹,在試婚紗的時候發(fā)現(xiàn)自己被綠了纤垂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡磷账,死狀恐怖峭沦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情逃糟,我是刑警寧澤吼鱼,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站绰咽,受9級特大地震影響菇肃,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜取募,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一琐谤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧玩敏,春花似錦斗忌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至砰粹,卻和暖如春唧躲,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背碱璃。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工弄痹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人厘贼。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓界酒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親嘴秸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345