<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          你真的了解Web Component嗎?

          共 29577字,需瀏覽 60分鐘

           ·

          2021-09-25 17:02

          為什么使用框架?

          對框架的理解

          作為現(xiàn)代前端開發(fā)者,擁抱框架是生存的不二法則,有些人一入場便投身框架的海洋,有些人則有幸見證過變革,從原生,到j(luò)q,到各種框架大行其道的今天。而當(dāng)前,國內(nèi)占領(lǐng)市場份額最多的要數(shù)vue、react和angular,他們都有著各自的特點(diǎn),這也是它們一路走來的立足之本。

          那么作為使用者的我們,在使用框架高效處理業(yè)務(wù)的同時,對框架本身也是需要一定程度的理解,以此來輔助我們更好的學(xué)習(xí)、了解和應(yīng)用框架。下面有一個表格,內(nèi)容提煉自尤雨溪本人對三大框架的對比看法,也許可以一定程度提升我們對框架的認(rèn)知。


          職責(zé)范圍的意義:

          • 大的職責(zé)范圍讓開發(fā)者習(xí)慣把問題拋給框架,
          • 小的職責(zé)范圍讓開發(fā)者習(xí)慣把問題拋給社區(qū)。

          框架的優(yōu)勢

          基于上述框架間的差異化,我們可以看出框架各自不同的設(shè)計(jì)、發(fā)展和其衍生出的生態(tài)其實(shí)都是源自于最初各自對于職責(zé)范圍界定的不同而來。但盡管差異不小,它們依然存在著共性,而共性,正是源于框架本身存在的意義和目標(biāo)。

          回頭審視,你會發(fā)現(xiàn)所有的框架其實(shí)都有共同的特點(diǎn)和目標(biāo),就是基于原生,然后更高的效率,更棒的性能,更好的差異抹平。

          但我們需要正確理解這句話,這并不意味著框架的指標(biāo)就優(yōu)于原生,而是說,因?yàn)橛辛丝蚣埽覀儾挥迷偈謱懖灰蕾嚇I(yè)務(wù)場景的數(shù)據(jù)-視圖的綁定,不用再手動抹平平臺或?yàn)g覽器之間的差異,不用再陷入操作dom的同時還要兼顧性能苦惱。可以說框架提高了開發(fā)者開發(fā)和實(shí)現(xiàn)功能的各項(xiàng)下限,讓快速開發(fā)和基礎(chǔ)性能之間更好的平衡。我們以react和vue為例,這兩大框架所帶來的優(yōu)勢包括但不限于:

          • 數(shù)據(jù)綁定(單/雙向)
          • 組件化開發(fā)(各種鉤子/生命周期/作用域隔離)
          • 虛擬dom(diff算法)以及路由等。
          • ......

          但這些優(yōu)勢不是憑空而來,就像vue的雙向綁定,從使用object.defineProperty轉(zhuǎn)為使用proxy,這種類似的實(shí)現(xiàn)或者說轉(zhuǎn)變,核心之處都需要js語法以及瀏覽器的原生支持。因?yàn)閣eb應(yīng)用最終都是要運(yùn)行在宿主--瀏覽器上的,所以制定規(guī)范的各大瀏覽器廠商以及提供原生api支持的瀏覽器環(huán)境才是王道,而框架不是。我們之所以需要引入各類的框架、工具庫去實(shí)現(xiàn)各種優(yōu)秀的設(shè)計(jì)與思想,比如組件化,本質(zhì)上是因?yàn)樵粗苯犹峁?yīng)的方式或是api,所以才需要框架去構(gòu)建棋盤之上的又一層規(guī)則體系,來實(shí)現(xiàn)開發(fā)者的訴求。

          而框架這種在瀏覽器原生規(guī)則之上又一層較高程度的封裝,在帶來便利高效的同時,不可避免的帶來兩個缺陷:

          • 性能的下降,這也是為什么上面說有時原生的直接操作指標(biāo)要優(yōu)于框架。下面是一些關(guān)于處理dom的react vs js的對比:

          (圖1:桌面chrome; 圖2:平板chrome; 圖3:移動端chrome;)(下圖:桌面chrome下react vs js 內(nèi)存比較)

          • 框架環(huán)境的隔離,例如vue的組件庫沒辦法很好的銜接在react的項(xiàng)目中(也許你會說vuera或微前端,但事實(shí)上ROI和性能并不好,開發(fā)和維護(hù)的成本較高)。

          那么如果原生可以提供某些api,是不是就可以一定程度上替代框架的某些功能,在擁有便利高效的同時,跨平臺、跨框架的使用,還能較大限度的保持原生的性能?

          這就是接下來要聊到的是web component和其所能帶來的可能甚至是變革。

          認(rèn)識web component

          web component

          狹義的來說,web component是瀏覽器環(huán)境提供的一些新的原生支持的api和模版。廣義的說,它是一套可以支持原生實(shí)現(xiàn)組件化的技術(shù)。從MDN的描述中可以看到,web component的誕生,是為了解決代碼復(fù)用、組件自定義、復(fù)用管理等問題。

          回看上文中,我們對框架優(yōu)勢的分析羅列,可以發(fā)現(xiàn)解決這些開發(fā)痛點(diǎn)的方案早已存在,也就是與之對應(yīng)的框架優(yōu)勢中的組件化。那么根據(jù)上面的分析,既然原生支持了,是不是意味著可以顛覆框架?這種想法是有些沖動的,單純依靠原生的api去顛覆框架是不現(xiàn)實(shí)的,能顛覆框架的也必須是框架,因?yàn)槊恳粋€框架都意味著對應(yīng)的生態(tài)(路由管理、狀態(tài)管理、dom性能優(yōu)化管理等)。如果有一天,當(dāng)前框架中的大部分優(yōu)秀的設(shè)計(jì)與思想被原生環(huán)境所吸收并支持,那么在此基礎(chǔ)上衍生的框架,才能真正具備替代當(dāng)前三大框架的能力,成為前端唯一一類框架。

          而現(xiàn)在,我們雖然還是無法舍棄框架擁抱原生,但是我們可以將其中的一部分進(jìn)行替代,使之擁有框架提供的優(yōu)勢,又能避免因框架而導(dǎo)致的缺陷。

          原生組件化能否替代框架組件化?

          我們先來看看組件化的特點(diǎn):

          • 高內(nèi)聚,低耦合
          • 標(biāo)記鮮明易維護(hù)
          • 塊狀接口易擴(kuò)展

          再看看依據(jù)組件化的規(guī)范,框架組件化提供給我們最直觀的體驗(yàn):

          • 高效復(fù)用
          • 作用域及樣式隔離
          • 自定義開發(fā)
          • 鉤子函數(shù)(生命周期)
          • ......

          最后我們來看看web component給我們提供了什么:

          • Custom elements:自定義元素,通過使用對應(yīng)的api,用戶可以在不依賴框架的情況下,開發(fā)原生層面的自定義元素,最關(guān)鍵的是,它將包含獨(dú)立的生命周期,以及提供了自定義屬性的監(jiān)聽。這就意味著它也同樣具備了較高的可操作性。
          • Shadow DOM:影子dom(最大的特點(diǎn)是不暴露給全局),你可以通過對應(yīng)的api,將shadow dom附加給你的自定義元素,并控制其相關(guān)功能。利用shadow dom的特性,起到隔離的作用,使特性保密,不用再擔(dān)心所編寫的腳本及樣式與文檔其他部分沖突。
          • HTML模版:通過<template/><slot/>去實(shí)現(xiàn)內(nèi)容分發(fā)。或者你可以回憶一下vue的插槽(slot)和react的props.children。但事實(shí)上,真的是vue最先創(chuàng)立的slot嗎?看下面~

          從上述這些原生api所提供給我們的種種特性,說明web component同樣可以滿足我們對組件的自定義及復(fù)用、與文檔其他部分隔離、生命周期的鉤子函數(shù),甚至是內(nèi)容分發(fā)等這些訴求。

          那么至少從理論的角度上說,web component是完全有能力替代框架組件化的,這意味著開發(fā)者可以在不使用的框架的前提下進(jìn)行組件化開發(fā),而且開發(fā)出的組件可以無縫嵌入使用了框架的項(xiàng)目中。有趣的是在最新發(fā)布的vue3.2中,也初步引入了對于web component的使用:

          兼容性

          作為開發(fā)者,面對新的強(qiáng)大的api,在充滿熱情的同時,更需要關(guān)注其可用性和普及范圍。我們可以通過can i use去查看它的兼容性:https://caniuse.com/?search=web%20component。從中我們可以看到:

          1. Custom elements兼容性

          2. Shadow DOM兼容性

          3. HTML templates兼容性

          自主定制元素和自定義內(nèi)置元素

          在Custom elements兼容性的描述中,我們看到兩個概念,如下:

          • 自主定制元素:獨(dú)立元素;它們不繼承自內(nèi)置的 HTML 元素。
          • 自定義內(nèi)置元素:這些元素繼承并擴(kuò)展了內(nèi)置的 HTML 元素。

          那么這里怎么去理解自主定制元素自定義內(nèi)置元素?我們可以從具體的code實(shí)現(xiàn)上進(jìn)行觀察:

          • 自主定制元素
          js: 
          ... 
          customElements.define('custom-elements'class)
          ... 
          html: 
          <body> 
          ... 
          <custom-elements></custom-elements> 
          ... 
          </body>
           
          • 自定義內(nèi)置元素
          js:
          ...
          customElements.define('custom-elements'class{ extends: 'p' });
          ...
          html:
          <body>
          ...
          <p is="custom-elements"></p>
          ...
          </body>

          可以看到從聲明上是沒有太大區(qū)別的,都是通過 customElements.define 去定義聲明,并且需要一個 class 去構(gòu)建內(nèi)部的生命周期與屬性監(jiān)聽。區(qū)別之處在于自定義內(nèi)置元素需要在后面的配置項(xiàng)中設(shè)置要繼承的內(nèi)置HTML 元素(指原生的元素)

          而最大的區(qū)別是在于使用上,自主定制元素其實(shí)就是一個完整的自定義組件,可以讓我們在不依賴任何框架的前提下實(shí)現(xiàn)組件化。而自定義內(nèi)置組件,可以理解為是對所繼承的原生元素的改造(如上述code呈現(xiàn),聲明定義自定義組件時,指定繼承的原生元素,后續(xù)使用該原生元素時,通過is屬性引用聲明的自定義組件,就可以改造該原生元素,使其擁有生命周期、自定義組件和作用域隔離的功能)。

          web component api的使用

          自定義組件的聲明和使用

          所依賴的主要接口是CustomElementRegistry,該接口提供了,用作支持自定義組件的使用和聲明:

          • window.customElements.define。

          該方法用來聲明自定義組件,接受3個參數(shù),無返回值:

          1. name:將要全局注冊的自定義組件名字(必須是中劃線的形式)。
          2. constructor:一個類,如果聲明的是自主定制元素,則必須繼承自HTMLElement;如果聲明的是自定義內(nèi)置元素,則必須繼承它將要擴(kuò)展的原生元素所屬的類(如要擴(kuò)展div,那就必須繼承HTMLDivElement)。并且類的構(gòu)造函數(shù)中,必須執(zhí)行super。
          3. options:一個可選的配置對象,只有在聲明自定義內(nèi)置元素時使用,且當(dāng)前只有一個配置項(xiàng)extends,值為將要擴(kuò)展的原生元素的標(biāo)簽名。

          聲明示例:

          //自主定制元素 
          class CustomEle extends HTMLElement 
            constructor() { 
              super(); 
              ... 
            } 

          customElements.define('custom-ele', CustomEle); 
           
          //自定義內(nèi)置元素,如果要擴(kuò)展div的話 
          class CustomEleBuiltIn extends HTMLDivElement 
            constructor() { 
              super(); 
              ... 
            } 

          customElements.define('custom-ele-build-in', CustomEleBuiltIn, { extends'div' }); 

          使用的方式也是多樣的。可以通過document.createElement的方式使用,也可以直接書寫在html中。使用示例:

          //自主定制元素 
          const customEle = document.createElement('custom-ele'); 
          customEle.setAttribute('img''https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fcdn.duitang.com%2Fuploads%2Fitem%2F201409%2F06%2F20140906020558_h4VfY.png'); 
          customEle.setAttribute('text''我是一段懸停說明'); 
          document.querySelector('#app').appendChild(customEle); 
          customElements.define('custom-ele', CustomEle); 
          //或 
          customElements.define('custom-ele', CustomEle); 
          const customEle = new CustomEle(); 
          customEle.setAttribute('img''https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fcdn.duitang.com%2Fuploads%2Fitem%2F201409%2F06%2F20140906020558_h4VfY.png'); 
          customEle.setAttribute('text''我是一段懸停說明'); 
          document.querySelector('#app').appendChild(customEle); 
          //或 
          <custom-ele img="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fcdn.duitang.com%2Fuploads%2Fitem%2F201409%2F06%2F20140906020558_h4VfY.png" text="我是一段懸停說明"> 
           
          //自定義內(nèi)置元素,如果要擴(kuò)展div的話 
          customElements.define('custom-ele-build-in', CustomEleBuiltIn, { extends: 'div' }); 
          const div = document.createElement('div', { is: 'custom-ele-build-in' }); 
          div.setAttribute('img', 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fcdn.duitang.com%2Fuploads%2Fitem%2F201409%2F06%2F20140906020558_h4VfY.png'); 
          div.setAttribute('text', '我是一段懸停說明'); 
          document.querySelector('#app').appNode.appendChild(div); 
          //或 
          <div is="custom-ele-build-in" img="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fcdn.duitang.com%2Fuploads%2Fitem%2F201409%2F06%2F20140906020558_h4VfY.png" text="我是一段懸停說明" /> 

          這里的幾 種使用方式其實(shí)還是有差異的,在初始化的時候,直接引用的方式可以在構(gòu)造階段就拿到掛載的各個屬性;但是采用create的方式時,構(gòu)造階段無法第一時間獲取屬性,當(dāng)然,利用生命周期的鉤子函數(shù),也是解決該問題的。

          • window.customElements.get。

          該方法用來獲取自定義組件的構(gòu)造函數(shù),接受一個參數(shù),即聲明過的自定義組件的name,返回構(gòu)造函數(shù)。

          const getCustomConstructorBefore = customElements.get('custom-ele'); 
          console.log('getCustomConstructor-before', getCustomConstructorBefore);//undefined 
          customElements.define('custom-ele', CustomEle); 
          const getCustomConstructorAfter = customElements.get('custom-ele'); 
          console.log('getCustomConstructor-after', getCustomConstructorAfter);//CustomEle 
          • window.customElements.upgrade。

          該方法是用來更新掛載主文檔之前的包含shadow dom的自定義組件的,接受一個參數(shù),即包含了shadow dom的自定義組件節(jié)點(diǎn),無返回值。(自定義組件在被append到主文檔的時候,會觸發(fā)自動更新)。

          //先創(chuàng)建了自定義元素 
          const customEle = document.createElement('custom-ele'); 
          customEle.setAttribute('img''https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fcdn.duitang.com%2Fuploads%2Fitem%2F201409%2F06%2F20140906020558_h4VfY.png'); 
          customEle.setAttribute('text''我是一段懸停說明'); 
          //后聲明自定義元素 
          customElements.define('custom-ele', CustomEle); 
          //結(jié)果為false,null 
          console.log(customEle instanceof CustomEle, customEle.shadowRoot); 
          //進(jìn)行更新節(jié)點(diǎn) 
          customElements.upgrade(customEle);//或document.querySelector('#app').appendChild(customEle); 
          //true,#document-fragment 
          console.log(customEle instanceof CustomEle, customEle.shadowRoot); 
          • window.customElements.whenDefined。

          該方法是用來檢測并提供自定義組件被定義聲明完畢的時機(jī)得,接受一個參數(shù),即自定義元素的name,返回值是一個promise(只檢測自定義組件是否被defined,不檢測是否被掛載于主文檔)。若提供的name無效,則觸發(fā)promise的catch。

          //創(chuàng)建了自定義元素dom 
          const customEle = document.createElement('custom-ele'); 
          customEle.setAttribute('img''https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fcdn.duitang.com%2Fuploads%2Fitem%2F201409%2F06%2F20140906020558_h4VfY.png'); 
          customEle.setAttribute('text''我是一段懸停說明'); 
          //用來判斷關(guān)閉定時器得標(biāo)識 
          let isStop = false
          //獲取自定義組件定義完畢的時機(jī) 
          customElements.whenDefined('custom-ele').then(() => { 
            console.log('定義完畢'); 
            isStop = true
          }); 
          //一個用于觀察得計(jì)時器 
          const timer = setInterval(() => { 
            if (isStop) { 
              clearInterval(timer); 
              return
            } 
            console.log(Math.floor(Date.now() / 1000)); 
          }, 1000); 
          //延遲3秒進(jìn)行自定義組件的定義及聲明 
          setTimeout(() => { 
            customElements.define('custom-ele', CustomEle); 
          }, 3000

          自定義組件的生命周期

          • constructor

          自定義組件的第一個生命周期,用來初始化自定義組件本身。觸發(fā)的時機(jī)在自定義組件被document.createElement的時候(前提是組件已經(jīng)被customElements.define過,如果組件是先create,后defined,那么constructor的執(zhí)行時機(jī)在append到主文檔里時)。

          class CustomEle extends HTMLElement 
            constructor() { 
              super(); 
              console.log('constructor被執(zhí)行'); 
              ...... 
            } 

           
          customElements.define('custom-ele', CustomEle); 
          const customEle = document.createElement('custom-ele'); 
          • connectedCallback

          在組件被成功添加到主文檔時觸發(fā)的生命周期,在constructor之后。

          class CustomEle extends HTMLElement 
            constructor() { 
              super(); 
              console.log('constructor被執(zhí)行'); 
              ...... 
            }  
            connectedCallback () { 
              console.log('connectedCallback被執(zhí)行'); 
            } 

           
          customElements.define('custom-ele', CustomEle); 
          const customEle = document.createElement('custom-ele'); 
          document.querySelector('#app').appendChild(customEle); 
          • attributeChangedCallback

          自定義組件最關(guān)鍵的一個生命周期。觸發(fā)時機(jī)在組件屬性被增加、刪除或修改的時候。如果你是在組件被append之前設(shè)置了屬性,那么就會在connectedCallback之前觸發(fā);反之,則在connectedCallback之后觸發(fā)。需要配合靜態(tài)方法observedAttributes來使用,只有注冊在observedAttributes中的屬性才會被監(jiān)聽。

          class CustomEle extends HTMLElement 
            constructor() { 
              super(); 
              console.log('constructor被執(zhí)行'); 
              ...... 
            }  
            connectedCallback () { 
              console.log('connectedCallback被執(zhí)行'); 
            } 
            static get observedAttributes () { return [ 'img''text' ]; } 
            attributeChangedCallback (name, oldValue, newValue) { 
              console.log('attributeChangedCallback', name) 
              if (name === 'img') { 
                this.shadowRoot.querySelector('img').src = this.getAttribute('img'); 
              } 
              if (name === 'text') { 
                this.shadowRoot.querySelector('.info').textContent = this.getAttribute('text'); 
              } 
            } 

           
          customElements.define('custom-ele', CustomEle); 
          customEle.setAttribute('img''https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fcdn.duitang.com%2Fuploads%2Fitem%2F201409%2F06%2F20140906020558_h4VfY.png'); 
          customEle.setAttribute('text''我是一段懸停說明'); 
          const customEle = document.createElement('custom-ele'); 
          document.querySelector('#app').appendChild(customEle); 
          • adoptedCallback

          當(dāng)元素被移動到新的文檔時,被調(diào)用。即元素是另一個文檔的元素,而adoptedCallback是新文檔下的自定義組件的回調(diào)。

          //聲明自定義組件的類 
          class CustomEle extends HTMLElement 
            constructor() { 
              super(); 
              ......   
            } 
            adoptedCallback () { 
              console.log('adoptedCallback被執(zhí)行'); 
            } 

          //創(chuàng)造場景,增加iframe,即舊文檔 
          appNode.innerHTML = '<iframe></iframe>'
          const p = document.createElement('p'); 
          p.innerHTML = 'iframe'
          appNode.querySelector('iframe').contentWindow.document.body.appendChild(p); 
           
          //新文檔中創(chuàng)建自定義組件 
          const customEle = document.createElement('custom-ele'); 
          customEle.setAttribute('img''https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fcdn.duitang.com%2Fuploads%2Fitem%2F201409%2F06%2F20140906020558_h4VfY.png'); 
          customEle.setAttribute('text''我是一段懸停說明'); 
          customElements.define('custom-ele', CustomEle); 
          appNode.appendChild(customEle); 
           
          //將元素從舊文檔遷移到新文檔 
          setTimeout(() => { 
            console.log('開始對元素進(jìn)行adoptNode操作'
            const node = appNode.querySelector('iframe').contentWindow.document.body.firstElementChild; 
            appNode.appendChild(document.adoptNode(node)) 
          }, 2000); 

          該回調(diào)函數(shù)并不常用,了解即可。

          • disconnectedCallback

          自定義組件的最后一個生命周期,觸發(fā)的時機(jī)在組件被成功從主文檔移除時。

          class CustomEle extends HTMLElement 
            constructor() { 
              super(); 
              ......   
            }  
            disconnectedCallback () { 
              console.log('disconnectedCallback被執(zhí)行'); 
            } 

           
          customElements.define('custom-ele', CustomEle); 
          const customEle = document.createElement('custom-ele'); 
          document.querySelector('#app').appendChild(customEle); 
          setTimeout(() => { 
            appNode.removeChild(customEle); 
          }, 2000

          注意:瀏覽器關(guān)閉或tabs關(guān)閉,不會觸發(fā)disconnectedCallback。

          Shadow DOM的使用

          其作用是將標(biāo)記結(jié)構(gòu)、樣式和行為隱藏起來,并與頁面上的其他代碼相隔離。Shadow DOM 都不是一個新事物,在過去的很長一段時間里,瀏覽器用它來封裝一些元素的內(nèi)部結(jié)構(gòu),回憶一下video標(biāo)簽內(nèi)部被隱藏起來的控制按鈕們。

          • 為元素附加Shadow DOM:ele.attachShadow

          attachShadow接受一個對象參數(shù),只需關(guān)注一個配置屬性mode,如果設(shè)置為open,表示可以從外部獲取Shadow DOM內(nèi)部的元素;如果設(shè)置為closed,則表示隱藏Shadow DOM內(nèi)部,例如<video>

          class CustomEle extends HTMLElement 
            constructor() { 
              super(); 
              const shadow = this.attachShadow({ mode'open' }); 
              ...... 
            } 

          customElements.define('custom-ele', CustomEle); 
          const customEle = document.createElement('custom-ele'); 
          document.querySelector('#app').appendChild(customEle); 
          console.log(customEle.shadowRoot) 

          若mode設(shè)置為closed:

          • 操作元素的Shadow DOM并添加樣式

            當(dāng)為一個元素附加了Shadow DOM后,就可以使用同操作正常dom一樣的方法去操作了。示例如下:

          class CustomEle extends HTMLElement 
            constructor() { 
              super(); 
              const shadow = this.attachShadow({ mode'open' }); 
           
              const wrapper = document.createElement('span'); 
              wrapper.setAttribute('class''wrapper'); 
           
              const icon = document.createElement('span'); 
              icon.setAttribute('class''icon'); 
           
              const info = document.createElement('span'); 
              info.setAttribute('class''info'); 
           
              const text = this.getAttribute('text'); 
              info.textContent = text; 
           
              const img = document.createElement('img'); 
              img.src = this.getAttribute('img'); 
              icon.appendChild(img); 
           
              const style = document.createElement('style'); 
              // console.log('CustomEle', style.isConnected); 
              style.textContent = 
                .wrapper { 
                  position: relative; 
                } 
                .info { 
                  font-size: 0.8rem; 
                  width: 200px; 
                  display: inline-block; 
                  border: 1px solid black; 
                  padding: 10px; 
                  background: white; 
                  border-radius: 10px; 
                  opacity: 0; 
                  transition: 0.6s all; 
                  position: absolute; 
                  bottom: 20px; 
                  left: 10px; 
                  z-index: 3; 
                } 
                img { 
                  width: 1.2rem; 
                } 
                .icon:hover + .info, .icon:focus + .info { 
                  opacity: 1; 
                } 
              `

              shadow.appendChild(style); 
              // console.log('CustomEle', style.isConnected); 
           
              shadow.appendChild(wrapper); 
              wrapper.appendChild(icon); 
              wrapper.appendChild(info); 
            } 

          const customEle = document.createElement('custom-ele'); 
          customEle.setAttribute('img''https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fcdn.duitang.com%2Fuploads%2Fitem%2F201409%2F06%2F20140906020558_h4VfY.png'); 
          customEle.setAttribute('text''我是一段懸停說明'); 
          customElements.define('custom-ele', CustomEle); 
          document.querySelector('#app').appendChild(customEle); 

          如果想添加樣式表,則可以把上述代碼中的代碼:

          const style = document.createElement('style'); 
              // console.log('CustomEle', style.isConnected); 
              style.textContent = 
                .wrapper { 
                  position: relative; 
                } 
                .info { 
                  font-size: 0.8rem; 
                  width: 200px; 
                  display: inline-block; 
                  border: 1px solid black; 
                  padding: 10px; 
                  background: white; 
                  border-radius: 10px; 
                  opacity: 0; 
                  transition: 0.6s all; 
                  position: absolute; 
                  bottom: 20px; 
                  left: 10px; 
                  z-index: 3; 
                } 
                img { 
                  width: 1.2rem; 
                } 
                .icon:hover + .info, .icon:focus + .info { 
                  opacity: 1; 
                } 
              `

              shadow.appendChild(style); 

          替換為:

          const linkElem = document.createElement('link'); 
          linkElem.setAttribute('rel''stylesheet'); 
          linkElem.setAttribute('href''style.css');//樣式的地址 
           
          shadow.appendChild(linkElem); 

          需要注意的是:由于link元素不會打斷 shadow root 的繪制, 因此在加載樣式表時可能會出現(xiàn)未添加樣式內(nèi)容(FOUC),導(dǎo)致閃爍。

          模版

          • template

          使用包裹的內(nèi)容不會在頁面上顯示,但是卻可以被js引用到。這就意味著有些內(nèi)容我們不用重復(fù)構(gòu)建多遍,使用<template></template>構(gòu)建一遍,然后多次引用處理就好了。

          class CustomEle extends HTMLElement 
            constructor() { 
              super(); 
              console.log('constructor被執(zhí)行'); 
              const shadow = this.attachShadow({ mode'open' }); 
           
              let template = document.getElementById('my-paragraph'); 
              if (template) { 
                let templateContent = template.content; 
                shadow.appendChild(templateContent.cloneNode(true)); 
              } 
              ...... 
            } 

          appNode.innerHTML = '<template id="my-paragraph"><style>p {color: white;background-color: #666;padding: 5px;}</style><p>My paragraph</p></template>'
          const customEle = document.createElement('custom-ele'); 
          customEle.setAttribute('img''https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fcdn.duitang.com%2Fuploads%2Fitem%2F201409%2F06%2F20140906020558_h4VfY.png'); 
          customEle.setAttribute('text''我是一段懸停說明'); 
          customElements.define('custom-ele', CustomEle); 
          appNode.appendChild(customEle); 
          • slot
            • 在template的基礎(chǔ)上,更加靈活的內(nèi)容分發(fā),可以配合template使用(在template中定義占位符,然后將template的內(nèi)容clone到shadow DOM中)。也可以直接在shadow DOM中添加占位符。

          然后在自定義組件的innerhtml中使用即可。

          class CustomEle extends HTMLElement 
            constructor() { 
              super(); 
              console.log('constructor被執(zhí)行'); 
              const shadow = this.attachShadow({ mode'open' }); 
           
              let template = document.getElementById('my-paragraph'); 
              if (template) { 
                let templateContent = template.content; 
                shadow.appendChild(templateContent.cloneNode(true)); 
              } 
              const slot2 = document.createElement('slot'); 
              slot2.setAttribute('name''newText2'); 
              shadow.appendChild(slot2); 
              ...... 
            } 

          appNode.innerHTML = '<template id="my-paragraph"><style>p {color: white;background-color: #666;padding: 5px;}</style><slot name="newText1"></slot></template><custom-ele><p slot="newText1">newText1</p></custom-ele>'
          const customEle = document.createElement('custom-ele'); 
          customEle.innerHTML = '<p slot="newText2">newText2</p>'
          customElements.define('custom-ele', CustomEle); 
          appNode.appendChild(customEle); 
          • slotchange:用于監(jiān)聽shadow DOM中的slot插入或移除的事件。
          class CustomEle extends HTMLElement 
            constructor() { 
              super(); 
              let template = document.getElementById('my-paragraph'); 
              if (template) { 
                let templateContent = template.content; 
                shadow.appendChild(templateContent.cloneNode(true)); 
              } 
              const slots = shadow.querySelectorAll('slot'); 
              slots.forEach(slot => { 
                slot.addEventListener('slotchange'function (e
                  console.log('slotchange', slot.name, e); 
                }); 
              }); 
              ...... 
            } 

           
          appNode.innerHTML = '<template id="my-paragraph">' + 
            '<style>p {color: white;background-color: #666;padding: 5px;}</style>' + 
            '<slot name="newText1"></slot>' + 
            '<slot name="spanText"></slot>' + 
            '</template>' + 
            '<h3>' + 
            '<custom-ele class="newText1Box">' + 
            '<p slot="newText1">newText1</p>' + 
            '<span slot="spanText">spanText</span>' + 
            '</custom-ele>' + 
            '</h3>'
          setTimeout(() => { 
            document.querySelector('.newText1Box').removeChild(document.querySelector('.newText1Box p')); 
            //或 
            document.querySelector('.newText1Box p').removeAttribute('slot'); 
          }, 2000
          在添加slot時(直接插入包含slot屬性的元素或給已插入的元素增加slot屬性)或刪除slot時(直接remove包含slot屬性的元素或給已插入的元素removeAttribute slot屬性),都會觸發(fā)slotchange事件。 

          相關(guān)的其他api

          • element.attachShadow(opt):用來給指定元素掛載shadow DOM。

          opt的配置項(xiàng):

          • mode:如果為open,表示可以在外部通過element.shadowRoot獲取shadow DOM節(jié)點(diǎn)。并且方法會返回shadow DOM對象。如果為closed,表示不允許外部訪問shadow DOM節(jié)點(diǎn),并且方法返回null。
          • delegatesFocus:表示是否減輕自定義元素的聚焦性能問題。當(dāng)shadow DOM中不可聚焦的部分被點(diǎn)擊時, 讓第一個可聚焦的部分成為焦點(diǎn), 并且shadow host將提供所有可用的 :focus 樣式.
          • css偽類:
            • :defined:表示所有內(nèi)置元素及已經(jīng)通過customElements.define注冊的元素。
            • :host:只能在shadow DOM的樣式表內(nèi)書寫。表示當(dāng)前所在的自定義組件的所有實(shí)例及shadow DOM下所有的元素。
            • :host([選擇器]):只能在shadow DOM的樣式表內(nèi)書寫。是:host的增強(qiáng),表示:host()所在的自定義組件的所有實(shí)例中選擇器符合括號中名稱的實(shí)例及其包含的shadow DOM下屬所有元素。
            • :host-context([選擇器]):只能在shadow DOM的樣式表內(nèi)書寫。是:host的增強(qiáng),表示:host()-context所在的自定義組件的所有實(shí)例的父元素中選擇器符合括號中名稱的實(shí)例及其包含的shadow DOM下屬所有元素。
            • :slotted([選擇器]):只能在shadow DOM的樣式表內(nèi)書寫。表示: slotted()所在的自定義組件的所有實(shí)例中選擇器符合括號中名稱的slot元素,若選擇器為*,則表示命中所有slot。
          • 節(jié)點(diǎn)相關(guān)拓展
            • getRootNode:使用方式為ele. getRootNode(opt),opt中包含一個屬性composed,為true時,檢索到的根元素為document;為false時,如果ele是屬于shadow DOM,那么檢索到shadow DOM,否則檢索到document。
            • isConnected:是元素的一個只讀屬性接口。返回元素是否與dom樹連接的boolean值。即是否被append到主文檔中。
          • event擴(kuò)展
            • composed屬性:用來指示該事件是否可以從 Shadow DOM 傳遞到一般的 DOM(測試后發(fā)現(xiàn)不論是普通DOM還是shadow DOM均為true)。
            • path屬性:返回事件的路徑。如果shadow root是使用mode為closed創(chuàng)建的,則不包括shadow樹中的節(jié)點(diǎn)(測試后發(fā)現(xiàn)盡管shadowdom設(shè)置了mode為closed,依然能獲取完整的path)。
          • 關(guān)于slot
            • ele.assignedSlot:用來獲取ele元素上代表插入slot的元素。但如果ele.attachShadow中的mode是closed為closed時,返回null。
            • ele.slot:用來獲取元素上slot的name值。
          • ......

          相關(guān)的庫及網(wǎng)站

          • webcomponents.org — site featuring web components examples, tutorials, and other information.
          • Hybrids — Open source web components library, which favors plain objects and pure functions over class and this syntax. It provides a simple and functional API for creating custom elements.
          • Polymer — Google's web components framework — a set of polyfills, enhancements, and examples. Currently the easiest way to use web components cross-browser.
          • Snuggsi.es — Easy Web Components in ~1kB Including polyfill — All you need is a browser and basic understanding of HTML, CSS, and JavaScript classes to be productive.
          • Slim.js — Open source web components library — a high-performant library for rapid and easy component authoring; extensible and pluggable and cross-framework compatible.
          • Smart.js — Web Components library with simple API for creating cross-browser custom elements.
          • Stencil — Toolchain for building reusable, scalable design systems in web components.

          參考

          • https://developer.mozilla.org/zh-CN/docs/Web/Web_Components

          • https://medium.com/jspoint/the-anatomy-of-web-components-d6afedb81b37

          • https://www.ruanyifeng.com/blog/2019/08/web_components.html 

          • https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements

          • https://developers.google.cn/web/fundamentals/web-components 

          • https://objectpartners.com/2015/11/19/comparing-react-js-performance-vs-native-dom/ 

          • https://bugs.webkit.org/show_bug.cgi?id=182671 

          瀏覽 52
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国内无码| 亚洲操操操 | 美女扒开腿秘 免费视频 | 中文字幕中文字幕无码 | 夜夜爽夜夜干天天摸天天干 |