整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          web components詳述

          web components詳述

          么是Web Components

          Web Components 是一套不同的技術(shù),允許您創(chuàng)建可重用的定制元素(它們的功能封裝在您的代碼之外)并且在您的web應(yīng)用中使用它們。
          背景

          • 組件化開發(fā)已經(jīng)是前端主流開發(fā)方式,因?yàn)榻M件化開發(fā)在代碼復(fù)用提升團(tuán)隊(duì)效率方面有著無(wú)可比擬的優(yōu)勢(shì),現(xiàn)在流行的React,Vue都是組件框架。
          • 谷歌公司一直在推動(dòng)瀏覽器的原生組件,即 Web Components API。相比第三方框架,原生組件簡(jiǎn)單直接,符合直覺,不用加載任何外部模塊,代碼量小。目前,它還在不斷發(fā)展,但已經(jīng)可用于生產(chǎn)環(huán)境。

          組件:最初的目的是代碼重用,功能相對(duì)單一或者獨(dú)立。在整個(gè)系統(tǒng)的代碼層次上位于最底層,被其他代碼所依賴,所以說(shuō)組件化是縱向分層。
          使用方法
          1. 創(chuàng)建一個(gè)類或函數(shù)來(lái)指定web組件的功能,推薦請(qǐng)使用 ECMAScript 2015 的類語(yǔ)法。
          2. 使用 CustomElementRegistry.define() 方法注冊(cè)自己的新自定義元素 ,并向其傳遞要定義的元素名稱、指定元素功能的類、以及可選的其所繼承自的元素。
          3. 如果需要的話,使用Element.attachShadow() 方法將一個(gè)shadow DOM附加到自定義元素上。使用原生的DOM方法向shadow DOM中添加子元素、事件監(jiān)聽器等。
          4. 如果需要的話,使用 <template>和<slot> 定義一個(gè)HTML模板。再次使用常規(guī)DOM方法克隆模板并將其附加到shadow DOM中。
          5. 最后在頁(yè)面中使用我們的自定義元素,就像使用原生HTML元素一樣
          寫一個(gè)Web Components組件
          1. 預(yù)期效果:預(yù)期渲染一個(gè)這樣的自定義品牌名片到頁(yè)面上面

          只要開發(fā)者在頁(yè)面上調(diào)用了<company-card></company-card>即可渲染頁(yè)面,根據(jù)規(guī)范,自定義元素的名稱必須包含連詞線,用與區(qū)別原生的 HTML 元素
          2. 自定義的元素需要使用js類來(lái)創(chuàng)建,頁(yè)面中所有的自定義元素都會(huì)是這個(gè)類的實(shí)例:

          然后就可以實(shí)現(xiàn)基礎(chǔ)效果:

          然后我們的js直接取出自定義元素上面的屬性賦值給對(duì)應(yīng)的標(biāo)簽即可

          3.使用template來(lái)創(chuàng)建元素

          • 如果使用原生js來(lái)創(chuàng)建組件,我們的開發(fā)效率會(huì)大打折扣,達(dá)不到我們想要的組件化提升效率的目的,所以瀏覽器提供了template來(lái)創(chuàng)建元素,重新寫一下樣式代碼。
          • 同時(shí)組件里面的數(shù)據(jù),我們可以通過(guò)在自定義標(biāo)簽上寫屬性,傳入到組件里面,更加符合組件開發(fā)的思路。

          然后我的js直接取出自定義元素上面的屬性賦值給對(duì)應(yīng)的標(biāo)簽即可。


          4. 加入樣式
          組件的樣式需要加入到template組件里面,為每個(gè)組件獨(dú)享的樣式,跟vue思路差不多最終的template可能是這樣的

          5. 最終實(shí)現(xiàn)效果

          生命周期函數(shù) 在custom element的構(gòu)造函數(shù)中,可以指定多個(gè)不同的回調(diào)函數(shù),它們將會(huì)在元素的不同生命時(shí)期被調(diào)用

          結(jié)語(yǔ):

          不得不承認(rèn),Web Components 標(biāo)準(zhǔn)的提出解決了一些問(wèn)題,必須交由瀏覽器去處理的是 Shadow DOM,在沒有Shadow DOM 的瀏覽器上實(shí)現(xiàn)代碼隔離的方式多多少少有缺陷。個(gè)人我覺得組件化的各個(gè) API 不夠簡(jiǎn)潔易用,依舊有 getElementById 這些的味道,但是交由各個(gè)類庫(kù)去簡(jiǎn)化也可以接受,而 import 功能上沒問(wèn)題,但是加載多個(gè)

          組件時(shí)性能問(wèn)題還是值得商榷,標(biāo)準(zhǔn)可能需要在這個(gè)方面提供更多給瀏覽器的指引,例如是否有可能提供一種單一請(qǐng)求加載多個(gè)組件 HTML 的方式等。

          在現(xiàn)在的移動(dòng)化趨勢(shì)中,Web Components 不僅僅是 Web 端的問(wèn)題,越來(lái)越多的開發(fā)者期望以 Web 的方式去實(shí)現(xiàn)移動(dòng)應(yīng)用,而多端復(fù)用的實(shí)現(xiàn)漸漸是以組件的形式鋪開,例如 React NativeWeex。所以 Web Components 的標(biāo)準(zhǔn)可能會(huì)影響到多端開發(fā) Web 化的一個(gè)模式和發(fā)展。

          但是,對(duì)于Web Components的發(fā)展前景還是比較看好,生產(chǎn)環(huán)境下還是觀望一下就好。

          絡(luò)技術(shù)的快速發(fā)展,帶來(lái)了層出不窮的新概念和框架,尤其是在前端開發(fā)領(lǐng)域,新技術(shù)的出現(xiàn)如同浪潮般一波接一波,例如 Vue3 和 Vite 的組合。而在這種技術(shù)快速更新的環(huán)境中,Web Components 作為一項(xiàng)已經(jīng)存在一段時(shí)間的技術(shù),為什么如今值得我們抓緊時(shí)間,去深入學(xué)習(xí)和探討呢?

          Web Components 是由 W3C 推動(dòng)的標(biāo)準(zhǔn)化技術(shù)。如今,它得到了包括 Chrome、Firefox、Safari 和 Edge 在內(nèi)的主流瀏覽器的廣泛支持。不僅 「Vue3」 的更新就包括了對(duì) Web Components 的原生支持,現(xiàn)在也出現(xiàn)了很多由Web Components封裝的「組件」「庫(kù)」,尤其是現(xiàn)在「面試」也成為了常問(wèn)的話題,其中更為頻頻出現(xiàn)的是 Shadow DOM。

          這項(xiàng)技術(shù)的魅力在于,「它允許開發(fā)者創(chuàng)建自定義、可重用的元素,這些元素可以在任何符合標(biāo)準(zhǔn)的 Web 應(yīng)用中無(wú)縫使用,而不受限于特定的框架(React、Vue)」。如果你還對(duì) Web Components 比較陌生,那么現(xiàn)在是時(shí)候開始了解這項(xiàng)技術(shù)了。

          Web Components 核心概念

          Web Components 是一種瀏覽器原生支持的 Web 組件化技術(shù),它允許開發(fā)者創(chuàng)建可重用的「自定義元素」,并且可以在任何支持 Web Components 的瀏覽器中使用。

          image.png

          Web Components 包括以下幾個(gè)核心概念:

          1. 「Custom Elements」(自定義元素):允許開發(fā)者創(chuàng)建新的 HTML 元素,并且可以定義它的行為和樣式。
          2. 「Shadow DOM」(影子 DOM):允許開發(fā)者封裝組件的內(nèi)部結(jié)構(gòu)和樣式,避免全局命名空間的污染。
          3. 「Templates」(模板):允許開發(fā)者定義一個(gè)可以在多個(gè)組件中重用的 HTML 結(jié)構(gòu)。
          4. 「Slots」(插槽):允許開發(fā)者創(chuàng)建一個(gè)可插入內(nèi)容的占位符,以便在不同的組件中使用。

          今天將圍繞這4個(gè)核心概念以及相關(guān)拓展,通過(guò)例子演示重點(diǎn)說(shuō)一下 Web Components 是如何創(chuàng)建可重用的自定義元素的。

          Custom Elements(自定義元素)

          Web Components 最大的特性之一就是能將 HTML 封裝成 Custom Elements(自定義元素)。下面我們通過(guò)一個(gè)簡(jiǎn)單的按鈕例子,看下它是怎么實(shí)現(xiàn)的。

          創(chuàng)建自定義元素

          首先,我們需要定義一個(gè)自定義元素。這可以通過(guò)使用 customElements.define() 方法來(lái)實(shí)現(xiàn)。在這個(gè)例子中,我們將創(chuàng)建一個(gè)名為 my-button 的自定義元素。

          // main.js
          class MyButton extends HTMLElement {
            constructor() {
              super();
              const shadowRoot=this.attachShadow({ mode: 'open' });
              shadowRoot.innerHTML=`
                <style>
                  button {
                    background-color: #4CAF50;
                    border: none;
                    color: white;
                    text-align: center;
                    text-decoration: none;
                    display: inline-block;
                    font-size: 16px;
                    margin: 4px 2px;
                    cursor: pointer;
                    padding: 10px 24px;
                    border-radius: 4px;
                  }
                </style>
                <button>Click Me!</button>
              `;
            }
          }
          customElements.define('my-button', MyButton);
          

          現(xiàn)在我們已經(jīng)定義了一個(gè)名為 my-button 的自定義元素,我們可以在 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 Example</title>
          </head>
          <body>
            <my-button></my-button>
            
            <script src="./main.js"></script>
          </body>
          </html>
          

          在這個(gè)例子中,我們創(chuàng)建了一個(gè)名為 my-button 的自定義元素,并在 HTML 文件中直接使用它。這個(gè)自定義元素將渲染為一個(gè)綠色的按鈕,上面寫著“Click Me!”。

          不止如此,CustomElements還支持自定義元素行為(如添加點(diǎn)擊事件),也就是說(shuō)既能封裝UI樣式,也是封裝UI交互。

          const shadowRoot=this.attachShadow({ mode: 'open' });
          shadowRoot.querySelector('button').addEventListener('click', ()=> {
              alert('按鈕被點(diǎn)擊了!');
          });
          

          到這里為止,便實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的 Web Components,詳細(xì)代碼見CustomElements。

          生命周期回調(diào)方法

          Custom Elements 也有一組生命周期回調(diào)方法(到這里是不是感覺 Web Component 就像 Vue、React似得,怎么還有生命周期?),這些方法在元素的不同生命周期階段被調(diào)用。這些生命周期方法允許你在元素的創(chuàng)建、插入文檔、更新和刪除等時(shí)刻執(zhí)行操作。

          以下是自定義元素的一些主要生命周期回調(diào)方法:

          1. 「constructor()」: 構(gòu)造函數(shù),在創(chuàng)建元素實(shí)例時(shí)調(diào)用。適用于執(zhí)行初始化操作,例如設(shè)置初始屬性、添加事件監(jiān)聽器或創(chuàng)建 Shadow DOM。
          2. 「connectedCallback()」: 當(dāng)自定義元素被插入到上下文時(shí)調(diào)用。適用于元素被插入到 DOM 時(shí)執(zhí)行的操作,例如獲取數(shù)據(jù)、渲染內(nèi)容或啟動(dòng)定時(shí)器。
          3. 「disconnectedCallback()」: 當(dāng)自定義元素從文檔中移除時(shí)調(diào)用。適用于元素從 DOM 中移除時(shí)執(zhí)行的操作,例如移除事件監(jiān)聽器或停止定時(shí)器。
          4. 「attributeChangedCallback(attributeName, oldValue, newValue)」: 當(dāng)自定義元素的屬性被添加、移除或更改時(shí)調(diào)用。要使用這個(gè)回調(diào),你需要在類中定義一個(gè)靜態(tài)的 observedAttributes 屬性,列出你想要監(jiān)聽的屬性。

          下面是一個(gè)簡(jiǎn)單的例子,展示了如何在自定義元素中使用這些生命周期方法:

          class MyCustomElement extends HTMLElement {
            constructor() {
              super();
              // 初始化操作,例如創(chuàng)建 Shadow DOM
              const shadowRoot=this.attachShadow({ mode: 'open' });
              shadowRoot.innerHTML='<p>這是一個(gè)自定義元素</p>';
            }
            connectedCallback() {
              // 元素被插入到 DOM 時(shí)執(zhí)行的操作
              console.log('Custom element connected to the DOM');
            }
            disconnectedCallback() {
              // 元素從 DOM 中移除時(shí)執(zhí)行的操作
              console.log('Custom element disconnected from the DOM');
            }
            attributeChangedCallback(name, oldValue, newValue) {
              // 監(jiān)聽的屬性發(fā)生變化時(shí)執(zhí)行的操作
              console.log(`Attribute ${name} changed from ${oldValue} to ${newValue}`);
            }
            static get observedAttributes() {
              // 返回一個(gè)數(shù)組,包含需要監(jiān)聽的屬性
              return ['my-attribute'];
            }
          }
          customElements.define('my-custom-element', MyCustomElement);
          

          在 HTML 中使用這個(gè)自定義元素:

          <my-custom-element my-attribute="value"></my-custom-element>
          

          當(dāng) my-custom-element 被插入到 DOM 中時(shí),connectedCallback 會(huì)被調(diào)用。如果元素被從 DOM 中移除,disconnectedCallback 會(huì)被調(diào)用。如果元素的 my-attribute 屬性發(fā)生變化,attributeChangedCallback 會(huì)被調(diào)用。

          ?

          「注意」:監(jiān)聽的同時(shí),也記得停止監(jiān)聽。比如說(shuō)你可能需要在元素連接到 DOM 時(shí)開始監(jiān)聽事件,但是在元素?cái)嚅_連接時(shí)停止監(jiān)聽,避免內(nèi)存泄漏。

          ?

          Shadow DOM(影子 DOM)

          下面我們將繼續(xù)探討 Shadow DOM,它是 Web Components 的核心特性之一。

          Shadow DOM

          Shadow DOM 允許開發(fā)者創(chuàng)建一個(gè)封閉的 DOM 子樹,這個(gè)子樹與主文檔的 DOM 分離,這意味著 Shadow DOM 內(nèi)部的樣式和結(jié)構(gòu)不會(huì)受到外部的影響,也不會(huì)影響到外部。

          在“Custom Elements(自定義元素)”的例子中,我們已經(jīng)簡(jiǎn)單使用了 Shadow DOM。

          「1、使用 innerHTML」

          通過(guò)設(shè)置 Shadow DOM 的 innerHTML 屬性,可以直接添加一個(gè)或多個(gè)元素。這種方式適用于從字符串模板快速填充 Shadow DOM。

          class MyElementInnerHTML extends HTMLElement {
            constructor() {
              super();
              const shadowRoot=this.attachShadow({ mode: 'open' });
              shadowRoot.innerHTML=`
                <style>
                  p { color: black; }
                </style>
                <p>使用 innerHTML</p>
              `;
            }
          }
          customElements.define('my-element-inner', MyElementInnerHTML);
          

          「2、使用 createElement 和 appendChild」

          也可以使用 document.createElement 方法創(chuàng)建一個(gè)新元素,然后使用 appendChild 方法將其添加到 Shadow DOM 中。

          const wrapper=document.createElement('p');
          wrapper.textContent='使用 createElement 和 appendChild';
          
          var style=document.createElement('style');
          style.textContent=`
          p { color: gray; }
          `;
          // 引入外部樣式同樣可以使用 appendChild
          // const linkElement=document.createElement('link');
          // linkElement.setAttribute('rel', 'stylesheet');
          // linkElement.setAttribute('href', 'style.css');
          class MyElementAppend extends HTMLElement {
            constructor() {
              super();
              const shadowRoot=this.attachShadow({ mode: 'open' });
              
              shadowRoot.appendChild(wrapper);
              shadowRoot.appendChild(style);
              // shadowRoot.appendChild(linkElement);
            }
          }
          customElements.define('my-element-append', MyElementAppend);
          

          3、template方式

          除上面兩種方式外,還可以使用模板元素 (<template>)添加,具體見下方 「“Templates(模版)”」

          Shadow Mode

          其中在自定義元素的構(gòu)造函數(shù)中,我們調(diào)用了 attachShadow() 方法,并傳入了一個(gè)對(duì)象 { mode: 'open' }。這里的 mode 屬性決定了 Shadow DOM 的封裝模式,它有兩個(gè)可能的值:

          • open:允許外部訪問(wèn) Shadow DOM 的 API。
          • closed:不允許外部訪問(wèn) Shadow DOM 的 API。

          在這個(gè)例子中,我們創(chuàng)建了一個(gè) Shadow DOM,并向其中添加了一行文字和相關(guān)的樣式。由于 Shadow DOM 的封裝性,這些樣式只會(huì)在 my-element 元素內(nèi)部生效,不會(huì)影響到頁(yè)面上的其他元素(樣式隔離)。

          下面我們更詳細(xì)地探討 Shadow DOM 是否允許外部訪問(wèn),的兩種封裝模式:open 和 closed。

          「1、Shadow Mode:open 模式」

          當(dāng)使用 open 模式創(chuàng)建 Shadow DOM 時(shí),外部腳本可以通過(guò) Element.shadowRoot 屬性訪問(wèn) Shadow DOM 的根節(jié)點(diǎn)。

          這意味著你可以從外部查詢、修改 Shadow DOM 內(nèi)部的元素和樣式。下面是一個(gè)使用 open 模式的例子:

          class OpenMyElement extends HTMLElement {
            constructor() {
              super();
              // 創(chuàng)建一個(gè) open 模式的 Shadow DOM
              const shadowRoot=this.attachShadow({ mode: 'open' });
              shadowRoot.innerHTML=`
                <style>
                  p { color: red; }
                </style>
                <p>這是一個(gè) open 模式的 Shadow DOM</p>
              `;
            }
          }
          customElements.define('open-my-element', OpenMyElement);
          
          // 在外部訪問(wèn) Shadow DOM
          const element=document.querySelector('open-my-element');
          console.log(element.shadowRoot); // 輸出 ShadowRoot 對(duì)象
          

          在這個(gè)例子中,我們創(chuàng)建了一個(gè)自定義元素 open-my-element,它有一個(gè) open 模式的 Shadow DOM。由于模式是 open,我們可以在外部通過(guò) element.shadowRoot 訪問(wèn) Shadow DOM 的根節(jié)點(diǎn),并進(jìn)行進(jìn)一步的操作,比如添加或刪除子元素,修改樣式等。

          image.png

          「2、Shadow Mode:closed 模式」

          當(dāng)使用 closed 模式創(chuàng)建 Shadow DOM 時(shí),外部腳本無(wú)法通過(guò) Element.shadowRoot 屬性訪問(wèn) Shadow DOM 的根節(jié)點(diǎn)。

          這意味著 Shadow DOM 內(nèi)部的元素和樣式對(duì)外部是完全隱藏的,無(wú)法從外部直接訪問(wèn)或修改。 下面是一個(gè)使用 closed 模式的例子:

          class ClosedMyElement extends HTMLElement {
            constructor() {
              super();
              // 創(chuàng)建一個(gè) closed 模式的 Shadow DOM
              const shadowRoot=this.attachShadow({ mode: 'closed' });
              shadowRoot.innerHTML=`
                <style>
                  p { color: blue; }
                </style>
                <p>這是一個(gè) closed 模式的 Shadow DOM</p>
              `;
            }
          }
          customElements.define('closed-my-element', ClosedMyElement);
          
          // 在外部嘗試訪問(wèn) Shadow DOM
          const element=document.querySelector('closed-my-element');
          console.log(element.shadowRoot); // 輸出 null
          
          

          在這個(gè)例子中,我們創(chuàng)建了一個(gè)自定義元素 closed-mode-element,它有一個(gè) closed 模式的 Shadow DOM。由于模式是 closed,當(dāng)我們嘗試在外部通過(guò) element.shadowRoot 訪問(wèn) Shadow DOM 的根節(jié)點(diǎn)時(shí),將得到 null。

          image.png

          open 和 closed 模式?jīng)Q定了 Shadow DOM 的封裝程度:

          • open 模式允許外部訪問(wèn) Shadow DOM 的 API,這意味著你可以從外部查詢和修改 Shadow DOM 內(nèi)部的元素和樣式。
          • closed 模式不允許外部訪問(wèn) Shadow DOM 的 API,這意味著 Shadow DOM 內(nèi)部的元素和樣式對(duì)外部是完全隱藏的,無(wú)法從外部直接訪問(wèn)或修改。

          選擇哪種模式取決于你的具體需求。如果你希望組件的內(nèi)部結(jié)構(gòu)和樣式完全對(duì)外部隱藏,使用 closed 模式是更好的選擇。如果你需要從外部訪問(wèn)和修改組件的內(nèi)部結(jié)構(gòu)和樣式,使用 open 模式會(huì)更合適。

          完整代碼,詳見ShadowDOM。

          其外,Shadow DOM 還支持更高級(jí)的用法,比如可以將 Shadow DOM 分割成多個(gè) Shadow Trees,使用 slots(插槽)來(lái)插入內(nèi)容,以及使用 template(模板)來(lái)定義可重用的 HTML 結(jié)構(gòu)。

          Slots(插槽)

          Slots 是一種特殊類型的元素,它允許你將內(nèi)容從組件的一個(gè)部分傳遞到另一個(gè)部分,增加了組件的靈活性。它使得 Web Components 自定義元素,更加的靈活。

          基礎(chǔ)使用

          例如,我們可以修改 my-button 組件,使其允許用戶自定義按鈕文本:

          class MyButton extends HTMLElement {
            constructor() {
              super();
              const shadowRoot=this.attachShadow({ mode: 'open' });
              shadowRoot.innerHTML=`
                <style>
                  /* ...樣式代碼保持不變... */
                </style>
                <button>
                    <slot>Click Me!</slot>
                </button>
              `;
            }
          }
          customElements.define('my-button', MyButton);
          

          現(xiàn)在,當(dāng)我們?cè)?HTML 中使用 my-button 時(shí),我們可以向其中插入任何內(nèi)容,它會(huì)替換掉 <slot> 標(biāo)簽:

          <my-button>Slots Custom Text</my-button>
          

          image.png

          命名插槽

          在開發(fā)中,我們更多的還會(huì)遇到不同情況下,選擇插入的內(nèi)容,這里就用到了命名插槽,使用起來(lái)非常方便。

          class MyButtonName extends HTMLElement {
            constructor() {
              super();
              const shadowRoot=this.attachShadow({ mode: 'open' });
              shadowRoot.innerHTML=`
                <style>
                  /* ...樣式代碼保持不變... */
                </style>
                <button>
                  <slot name="element-name"></slot>
                  <slot name="element-age"></slot>
                  <slot name="element-email"></slot>
                </button>
              `;
            }
          }
          customElements.define('my-button-name', MyButtonName);
          
          <my-button-name>
              <span slot="element-name">element-name</span>
          </my-button-name>
          <my-button-name>
              <span slot="element-age">element-age</span>
          </my-button-name>
          <my-button-name>
              <span slot="element-email">element-email</span>
          </my-button-name>
          

          image.png

          是不是很方便,很靈活!!具體代碼詳見Web Components Slots。

          Templates(模板)

          Templates 允許你定義一個(gè)可以在多個(gè)組件中重用的 HTML 結(jié)構(gòu)。你可以將模板放在 HTML 文件中的任何位置,并通過(guò) JavaScript 動(dòng)態(tài)地實(shí)例化它們:

          <my-button></my-button>
          
          <template id="my-button-template">
            <style>
              /* ...樣式代碼保持不變... */
            </style>
            <button>
                <slot>Click Me!</slot>
            </button>
          </template>
          

          在 JavaScript 中,你可以這樣使用模板:

          class MyButton extends HTMLElement {
            constructor() {
              super();
              const shadowRoot=this.attachShadow({ mode: 'open' });
              const template=document.getElementById('my-button-template');
              // 使用`cloneNode()` 方法添加了拷貝到 Shadow root 根節(jié)點(diǎn)上。
              shadowRoot.appendChild(template.content.cloneNode(true));
            }
          }
          customElements.define('my-button', MyButton);
          

          image.png

          這樣,你就可以在不同的組件中重用同一個(gè)模板,從而提高代碼的可維護(hù)性和重用性。具體代碼下詳見Web Components Templates。

          相關(guān)拓展

          Web Components 兼容性

          Web Components 是一組用于構(gòu)建可復(fù)用組件的技術(shù),包括 Custom Elements, Shadow DOM, HTML Templates 等。這些技術(shù)的出現(xiàn),使得開發(fā)者能夠更好地組織,去開發(fā)復(fù)雜的網(wǎng)頁(yè)應(yīng)用。然而,由于這些技術(shù)相對(duì)較新,不同瀏覽器的支持情況不盡相同,因此兼容性問(wèn)題也是我們需要重點(diǎn)關(guān)注的方向。

          Custom Elements

          image.png

          Shadow DOM

          image.png

          HTML Templates

          image.png

          從上面可以看出,現(xiàn)階段市場(chǎng)上大部分的瀏覽器已經(jīng)都原生支持了 Web Components 的規(guī)范標(biāo)準(zhǔn)。「但是如果說(shuō)出現(xiàn)了兼容性問(wèn)題,我們應(yīng)該怎么處理?」

          Polyfills

          對(duì)于舊版瀏覽器不支持的兼容性情況,可以考慮使用 polyfill 來(lái)實(shí)現(xiàn)兼容性。Polyfills 是一種代碼注入技術(shù),使得瀏覽器可以支持新的標(biāo)準(zhǔn) API。對(duì)于不支持 Web Components 的瀏覽器,我們可以用 Polyfills 讓這些瀏覽器可以支持 Web Components。

          這里我們可以用到 webcomponents.js 庫(kù),它可以實(shí)現(xiàn)兼容 Custom Elements、Shadow DOM 和 HTML Templates 標(biāo)準(zhǔn),讓我們?cè)陂_發(fā)時(shí)不必考慮兼容性問(wèn)題。

          npm install @webcomponents/webcomponentsjs
          
          <!-- load webcomponents bundle, which includes all the necessary polyfills -->
          <script src="node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
          
          <!-- load the element -->
          <script type="module" src="my-element.js"></script>
          
          <!-- use the element -->
          <my-element></my-element>
          

          具體配置詳情,見polyfills webcomponents。

          React 與 Vue

          相信大家也比較關(guān)心 Web Components 與現(xiàn)有框架(如 React、Vue)相比有哪些優(yōu)勢(shì)?以及各自適用場(chǎng)景?

          首先,Web Components 是一組 Web 平臺(tái) API,允許開發(fā)者創(chuàng)建可重用的自定義元素,而無(wú)需依賴于任何特定的框架。與現(xiàn)有的前端框架,Web Components 有以下幾個(gè)優(yōu)勢(shì):

          1. 「標(biāo)準(zhǔn)化」:Web Components 是基于 Web 標(biāo)準(zhǔn)(如 Custom Elements、Shadow DOM 和 HTML Templates)構(gòu)建的,這意味著它們得到了瀏覽器廠商的直接支持,而不依賴于任何特定的庫(kù)或框架。
          2. 「輕量級(jí)」:Web Components 不需要額外的庫(kù)或框架即可工作,這可以減少應(yīng)用程序的依賴性和大小,特別是在不需要框架其他功能的情況下。
          3. 「封裝性」:通過(guò) Shadow DOM,Web Components 可以將標(biāo)記結(jié)構(gòu)、樣式和腳本封裝在一起,避免全局樣式和腳本的沖突,保證了組件的獨(dú)立性和重用性。
          4. 「易于集成」:Web Components 可以與現(xiàn)有的框架(如 React 和 Vue)集成,開發(fā)者可以在這些框架中使用 Web Components,或者將現(xiàn)有的框架組件封裝成 Web Components 以供其他項(xiàng)目使用。

          然而,Web Components 也有其局限性,例如:

          • 「生態(tài)系統(tǒng)」:與 React 和 Vue 等成熟框架相比,Web Components 的生態(tài)系統(tǒng)較小,社區(qū)支持和資源可能不如這些框架豐富。
          • 「功能限制」:Web Components 本身不提供狀態(tài)管理、路由等高級(jí)功能,這些通常需要額外的庫(kù)或框架來(lái)實(shí)現(xiàn)。
          • 「性能」:對(duì)于復(fù)雜的應(yīng)用程序,一些框架(如 React)通過(guò)虛擬 DOM 等技術(shù)提供了更高的性能優(yōu)化,而 Web Components 需要開發(fā)者手動(dòng)優(yōu)化。

          總的來(lái)說(shuō),「Web Components 提供了一種標(biāo)準(zhǔn)化且框架無(wú)關(guān)的方式來(lái)構(gòu)建組件,適合組件庫(kù)的開發(fā)」。而框架如 「React、Vue 則在生態(tài)系統(tǒng)支持、開發(fā)體驗(yàn)和數(shù)據(jù)處理方面有明顯優(yōu)勢(shì),適合快速開發(fā)復(fù)雜的應(yīng)用程序」

          實(shí)際應(yīng)用案例

          1. 「Vue3」: Vue3 引入了對(duì) Web Components 的原生支持,通過(guò)所謂的 “Vue Components”,它允許將 Vue 組件轉(zhuǎn)換為 Web Components。
          2. MicroApp:基于 Web Components 的一款簡(jiǎn)約、高效、功能強(qiáng)大的微前端框架。
          3. Twitter:Twitter 2016 年開始將自己的嵌入式推文 從 iframe 切換成 ShadowDOM,減少了內(nèi)存消耗、加快了渲染速度,并批量渲染的時(shí)候保持絲滑。
          4. svelte + vite 開發(fā) Web Components:通過(guò)svelte + vite快速搭建 web components 的項(xiàng)目。
          5. 使用Polymer構(gòu)建Web Components:用于構(gòu)建Web Component,它提供了一套工具和API,能夠更容易地創(chuàng)建自定義元素。

          參考資料

          1. 「MDN Web Docs - Web Components 入門」
          2. 你不知道的Web Components - 現(xiàn)狀
          3. 自定義元素 v1 - 可重復(fù)使用的網(wǎng)絡(luò)組件
          4. Web Components Tutorial for Beginners [2019]

          總結(jié)

          Web Components 是 W3C 推動(dòng)的標(biāo)準(zhǔn)化技術(shù),它通過(guò)自定義元素的方式,允許開發(fā)者在瀏覽器中直接使用。這種技術(shù)通過(guò) Shadow DOM 實(shí)現(xiàn)了組件化 DOM 隔離和樣式隔離,確保了組件的獨(dú)立性和可重用性,這些特性被現(xiàn)有很多借鑒和使用。

          希望這篇文章對(duì)你有所幫助!!!歡迎在評(píng)論區(qū),一起討論。

          今前端編程中,利用語(yǔ)義化的 HTML 結(jié)合 CSS 來(lái)完一個(gè)組件并不是一件難事,這也意味著無(wú)論在 ReactVue 中都可以插入,不過(guò)它倆不是今天的主角,接下來(lái)我將用一個(gè)例子來(lái)介紹如何封裝一個(gè)完整的原生 HTMLWeb Components 組件,讓我們開始吧!

          HTML結(jié)構(gòu)

          首先我們來(lái)了解下 HTML 中的 <details> 元素,它可以用于創(chuàng)建一個(gè)小部件,其中包含僅在小部件處于“打開”狀態(tài)時(shí)才可見的附加信息,<details>元素內(nèi)可以包含的內(nèi)容沒有任何限制。

          默認(rèn)情況下,元素創(chuàng)建的小部件<details>處于“關(guān)閉”狀態(tài)(open標(biāo)簽可使其打開)。通過(guò)單擊小部件在“打開”和“關(guān)閉”狀態(tài)之間切換,以顯示或隱藏標(biāo)簽中包含的附加信息,內(nèi)部標(biāo)簽 <summary> 元素則可為該部件提供概要。

          一個(gè)簡(jiǎn)單的例子如下:

          <details>
              <summary> 不能說(shuō)的秘密 </summary>
              藏的這么深,可還是被你發(fā)現(xiàn)了
          </details>
          details {
              border: 1px solid #aaa;
              border-radius: 4px;
              padding: .5em .5em 0;
          }
          
          summary {
              font-weight: bold;
              margin: -.5em -.5em 0;
              padding: .5em;
          }
          
          details[open] {
              padding: .5em;
          }
          
          details[open] summary {
              border-bottom: 1px solid #aaa;
              margin-bottom: .5em;
          }

          使用語(yǔ)義化 HTML 的優(yōu)點(diǎn):頁(yè)面內(nèi)容結(jié)構(gòu)更清晰,方便開發(fā)者閱讀,更利于瀏覽器的理解與加載,搜索引擎解析與SEO優(yōu)化。

          添加億點(diǎn)樣式

          原生元素默認(rèn)的樣式很簡(jiǎn)陋,因此我們需要為其定制一下樣式,這塊內(nèi)容我們簡(jiǎn)單帶過(guò),只講解關(guān)鍵部分,樣式內(nèi)容有省略,具體可以在文末的碼上掘金中看到呈現(xiàn)效果。

          .ContentWarning > summary {
            position: relative;
            list-style: none; /** 去除默認(rèn)樣式 **/
            user-select: none; 
            cursor: pointer;
            /** 為其添加一個(gè)斜線背景 **/
            --stripe-color: rgb(0 0 0 / 0.1);
            background-image: repeating-linear-gradient(45deg,
                transparent,
                transparent 0.5em,
                var(--stripe-color) 0.5em,
                var(--stripe-color) 1em);
          }
          
          /** 通過(guò)var變量調(diào)整懸停時(shí)的顏色樣式 **/
          .ContentWarning>summary: hover,
          .ContentWarning>summary: focus {
            --stripe-color: rgb(150 0 0 / 0.1);
          }

          封裝模板

          現(xiàn)在我們來(lái)把它封裝成一個(gè)完整的組件,這需要先將 HTML 編寫在模板 template 當(dāng)中,并設(shè)置一個(gè) id,如下所示:

          <template id="warning-card">  
            <details class="ContentWarning">
              <summary>
                <strong>?? 注意:</strong> 以下為隱藏內(nèi)容
              </summary>
              <slot name="desc"> 藏的這么深,可還是被你發(fā)現(xiàn)了 </slot>
            </details>
          </template>

          熟悉 Vue 的小伙伴應(yīng)該很容易理解上面的代碼,結(jié)構(gòu)很相似,不過(guò)網(wǎng)頁(yè)不會(huì)直接渲染它包裹的內(nèi)容。此外我們還對(duì)此模板設(shè)置了一個(gè)插槽 slot,后面會(huì)講到它的作用。

          定義組件

          有了上面封裝好的模板,我們就需要在 JS 中定義成可用組件來(lái)讓其能夠被使用,調(diào)用 window 下的 customElements.define 方法,第一個(gè)參數(shù)是傳入組件名稱,我們定義組件名為: warning-card ,第二個(gè)參數(shù)傳入一個(gè)繼承了 HTMLElement 的類,在其構(gòu)造方法當(dāng)中獲取并克隆一個(gè)新的 HTML 節(jié)點(diǎn),它會(huì)通過(guò) appendChild 渲染到頁(yè)面當(dāng)中。

          window.customElements.define('warning-card',
            class extends HTMLElement {
              constructor() {
                super();
                var templateElem=document.getElementById('warning-card');
                var content=templateElem.content.cloneNode(true);
                this.appendChild(content);
              }
            })

          接著我們就可以在頁(yè)面中把它當(dāng)作組件那樣使用了:

          <warning-card> </warning-card>

          插槽與傳參

          回頭看看上面我們模板中設(shè)置的插槽 slot,此時(shí)還是沒有生效的,我們需要稍微改寫一下構(gòu)造函數(shù)中的渲染方式,將 web 組件定義為一個(gè) Shadow DOM,這樣構(gòu)造的是一個(gè)可以將標(biāo)記結(jié)構(gòu)、樣式和行為隱藏起來(lái),并與頁(yè)面上的其他代碼相隔離,保證不同的部分不會(huì)混在一起的獨(dú)立元素,并在最后使用 Node.cloneNode() 方法添加了模板的拷貝到 Shadow 的根結(jié)點(diǎn)上。

          window.customElements.define('warning-card',
            class extends HTMLElement {
              constructor() {
                super();
                var template=document.getElementById('warning-card').content;
                this.attachShadow({ mode: 'open' }).appendChild(template.cloneNode(true));
              }
            })

          現(xiàn)在我們嘗試使用下組件,往其內(nèi)容添加一個(gè)圖片,指向名為 descslot 插槽中:

          <warning-card>
            <img slot="desc" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ba825ffee78c4a1b9c0232e5d2f1d048~tplv-k3u1fbpfcp-watermark.image?" />
          </warning-card>

          這時(shí)你會(huì)發(fā)現(xiàn),圖片插入到 details 元素的隱藏區(qū)域當(dāng)中了,slot 已經(jīng)成功生效,但是樣式卻消失了,這時(shí)因?yàn)榻M件已經(jīng)被完全隔離,我們需要將樣式作用在其內(nèi)部才會(huì)生效。

          <template id="warning-card">
            <style>
              <!-- TODO: 組件的樣式 -->
            </style>
            
            <details class="ContentWarning">
              <summary>
                <strong>?? 注意:</strong>
              </summary>
              <slot name="desc">THE DESCRIPTION</slot>
            </details>
          </template>

          這樣組件就正常了:

          除了定制模板中的插槽,我們也可以通過(guò) HTML 標(biāo)簽屬性來(lái)實(shí)現(xiàn)一些簡(jiǎn)單的傳參,例如在 summary 標(biāo)簽中顯示一個(gè)標(biāo)題:

          <warning-card title="前方高能">
          </warning-card>

          我們只需要在模板中定義好這個(gè)標(biāo)題的位置:

          <template id="warning-card">
            <details class="ContentWarning">
              <summary>
                  <!-- TODO: 模板中加入一個(gè)span標(biāo)簽 -->
                <strong>?? 注意:</strong> <span id="title"></span>
              </summary>
            </details>
          </template>

          最后在構(gòu)造函數(shù)中我們通過(guò) document 的原生方法寫入模板中就可以了:

          window.customElements.define('warning-card',
            class extends HTMLElement {
              constructor() {
                super();
                var template=document.getElementById('warning-card').content;
                // TODO: 找到title標(biāo)簽,寫入傳入組件的title屬性值
                template.querySelector('#title').innerText=this.getAttribute('title');
                this.attachShadow({ mode: 'open' }).appendChild(template.cloneNode(true));
              }
            })

          結(jié)束

          至此,我們通過(guò)一個(gè)簡(jiǎn)單的原生組件學(xué)習(xí)了如何編寫 Web Components,可以在此代碼片段中查看具體源碼:原生Web Components組件 - 碼上掘金原生Web Components組件 - 碼上掘金。

          以上就是文章的全部?jī)?nèi)容,希望對(duì)你有所幫助!如果覺得文章寫的不錯(cuò),可以點(diǎn)贊收藏,也歡迎關(guān)注,我會(huì)持續(xù)更新更多前端有用的知識(shí)與實(shí)用技巧,我是茶無(wú)味de一天,希望與你共同成長(zhǎng)~


          主站蜘蛛池模板: 精品国产一区二区三区久久| 中文字幕一区二区三区视频在线| 国产精品香蕉在线一区| 精品香蕉一区二区三区| 一区二区三区精品高清视频免费在线播放| 无码人妻精品一区二| 91精品一区二区综合在线| 国产激情无码一区二区三区| 伊人久久一区二区三区无码| 国产午夜毛片一区二区三区| 国产综合视频在线观看一区 | 人妻无码一区二区三区四区| 国产乱码一区二区三区| 无码人妻精品一区二区三区东京热| 亚洲精品国产suv一区88| 国产精品久久一区二区三区| 国产一区二区免费| 无码人妻视频一区二区三区 | av在线亚洲欧洲日产一区二区| 亚洲av日韩综合一区二区三区 | 3d动漫精品啪啪一区二区中| 手机看片福利一区二区三区| 国产在线不卡一区二区三区| 一区二区日韩国产精品| 亚洲中文字幕无码一区| 日韩人妻一区二区三区免费| 亚洲av无码不卡一区二区三区| 亚洲AV无码一区二区三区DV | 久久久精品人妻一区二区三区蜜桃 | 亚洲AV无码一区二区三区牛牛| 一本一道波多野结衣一区| 国产精品一区二区三区高清在线| 国产日韩精品一区二区在线观看播放 | 一区二区三区中文字幕| 精品少妇一区二区三区视频| 中文字幕AV一区二区三区人妻少妇| 无码视频一区二区三区| 国产一区三区二区中文在线| 亚洲欧美日韩中文字幕在线一区| 国产亚洲福利一区二区免费看 | 亚洲熟妇av一区二区三区漫画|