<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>

          每天 React, Vue, 你知道如何原生實現(xiàn) WebComponent嗎?

          共 22177字,需瀏覽 45分鐘

           ·

          2021-12-01 03:06

          大廠技術(shù)  高級前端  Node進階

          點擊上方 程序員成長指北,關(guān)注公眾號

          回復1,加入高級Node交流群

          原文地址:https://juejin.cn/post/7034796986889043999

          作者:hpstream_ (感謝小伙伴投稿)

          談到WebComponent 很多人很容易想到Vue,React中的組件。但其實H5原生也已經(jīng)支持了組件的編寫。

          查看 Web Components MDN 文檔,里面原話如下:

          Web Components

          Web Components 是一套不同的技術(shù),允許您創(chuàng)建可重用的定制元素(它們的功能封裝在您的代碼之外)并且在您的web應用中使用它們。Web Components旨在解決這些問題 — 它由三項主要技術(shù)組成,它們可以一起使用來創(chuàng)建封裝功能的定制元素,可以在你喜歡的任何地方重用,不必擔心代碼沖突。

          Custom elements(自定義元素):一組JavaScript API,允許您定義custom elements及其行為,然后可以在您的用戶界面中按照需要使用它們。

          Shadow DOM(影子DOM):一組JavaScript API,用于將封裝的“影子”DOM樹附加到元素(與主文檔DOM分開呈現(xiàn))并控制其關(guān)聯(lián)的功能。通過這種方式,您可以保持元素的功能私有,這樣它們就可以被腳本化和樣式化,而不用擔心與文檔的其他部分發(fā)生沖突。

          • Custom elements(自定義元素):一組JavaScript API,允許您定義custom elements及其行為,然后可以在您的用戶界面中按照需要使用它們。

          • Shadow DOM(影子DOM):一組JavaScript API,用于將封裝的“影子”DOM樹附加到元素(與主文檔DOM分開呈現(xiàn))并控制其關(guān)聯(lián)的功能。通過這種方式,您可以保持元素的功能私有,這樣它們就可以被腳本化和樣式化,而不用擔心與文檔的其他部分發(fā)生沖突。

          • HTML templates(HTML模板):template 和 slot 元素使您可以編寫不在呈現(xiàn)頁面中顯示的標記模板。然后它們可以作為自定義元素結(jié)構(gòu)的基礎(chǔ)被多次重用。

          上面的概念難以理解,我們通過一個例子看下如何編寫一個組件;

          案例一

          1. 什么是 HTML templates(HTML模板)?
           <template id="btn">
              <button class="hp-button">
                <slot></slot>
              </button>
            </template>
          1. Custom elements(自定義元素)
          class HpButton extends HTMLElement {
                constructor() {
                  super();
                  //...
                  
                }
              }
              // 定義了一個自定義標簽 組件
          window.customElements.define('hp-button', HpButton)
          1. Shadow DOM(影子DOM)
           let shadow = this.attachShadow({
                    mode'open'
                  });
                  let btnTmpl = document.getElementById('btn');
                  let cloneTemplate = btnTmpl.content.cloneNode(true)
                  const style = document.createElement('style');
                  let type = this.getAttribute('type') || 'default';
                  const btnList = {
                    'primary': {
                      background'#ff0000',
                      color'#fff'
                    },
                    'default': {
                      background'#909399',
                      color'#fff'
                    }
                  }
                  style.textContent = `
                              .hp-button{
                                  outline:none;
                                  border:none;
                                  border-radius:4px;
                                  padding:5px 20px;
                                  display:inline-flex;
                                  background:${btnList[type].background};
                                  color:${btnList[type].color};
                                  cursor:pointer
                              }
                          `

          // dom操作具備移動型
            shadow.appendChild(style)
           shadow.appendChild(cloneTemplate)

          一個簡單完整的例子

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

          <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Document</title>
          </head>

          <body>
            <style>
              :root {
                --background-color: black;
                --text-color: yellow
              }
            
          </style>
            <hp-button type="primary">
              <input type="text">
              按鈕
            </hp-button>
            <hp-button>珠峰按鈕</hp-button>
            <!-- 內(nèi)容是不會被渲染到視圖上,不會影響頁面展示,可以使用模板 -->
            <template id="btn">
              <button class="hp-button">
                <slot></slot>
              </button>
            </template>

            <script>
              class HpButton extends HTMLElement {
                constructor() {
                  super();
                 
                  let shadow = this.attachShadow({
                    mode'open'
                  });
                  let btnTmpl = document.getElementById('btn');
                  let cloneTemplate = btnTmpl.content.cloneNode(true)
                  const style = document.createElement('style');
                  let type = this.getAttribute('type') || 'default';
                  const btnList = {
                    'primary': {
                      background'#ff0000',
                      color'#fff'
                    },
                    'default': {
                      background'#909399',
                      color'#fff'
                    }
                  }
                  style.textContent = `
                              .hp-button{
                                  outline:none;
                                  border:none;
                                  border-radius:4px;
                                  padding:5px 20px;
                                  display:inline-flex;
                                  background:${btnList[type].background};
                                  color:${btnList[type].color};
                                  cursor:pointer
                              }
                          `

                  // dom操作具備移動型
                  shadow.appendChild(style)
                  shadow.appendChild(cloneTemplate)
                }
              }
              // 定義了一個自定義標簽 組件
              window.customElements.define('hp-button', HpButton)
            
          </script>
          </body>
          </html>

          結(jié)論:原生組件與Vue,React的組件的概念是相似的,但是從寫法上來看有區(qū)別。

          深入學習

          組件中還有重點的兩部分:生命周期和事件。

          生命周期

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

          • connectedCallback:當 custom element首次被插入文檔DOM時,被調(diào)用。
          • disconnectedCallback:當 custom element從文檔DOM中刪除時,被調(diào)用。
          • adoptedCallback:當 custom element被移動到新的文檔時,被調(diào)用。
          • attributeChangedCallback: 當 custom element增加、刪除、修改自身屬性時,被調(diào)用。

          我們來看一下它們的一下用法示例。下面的代碼出自life-cycle-callbacks示例(查看在線示例:https://mdn.github.io/web-components-examples/life-cycle-callbacks/)。這個簡單示例只是生成特定大小、顏色的方塊。custom element看起來像下面這樣

          生命周期的代碼的具體示例:

          class Square extends HTMLElement {
            // Specify observed attributes so that
            // attributeChangedCallback will work
            static get observedAttributes() {
              return ['c''l'];
            }

            constructor() {
              // Always call super first in constructor
              super();

              const shadow = this.attachShadow({mode'open'});

              const div = document.createElement('div');
              const style = document.createElement('style');
              shadow.appendChild(style);
              shadow.appendChild(div);
            }

            connectedCallback() {
              console.log('Custom square element added to page.');
              updateStyle(this);
            }

            disconnectedCallback() {
              console.log('Custom square element removed from page.');
            }

            adoptedCallback() {
              console.log('Custom square element moved to new page.');
            }

            attributeChangedCallback(name, oldValue, newValue) {
              console.log('Custom square element attributes changed.');
              updateStyle(this);
            }
          }

          customElements.define('custom-square', Square);

          事件

          可以采用 disatchEventCustomEvent 來實現(xiàn):

           document.querySelector('???').dispatchEvent(new CustomEvent('changeName', {
                  detail: {
                    name1111,
                  }
                }))

          折疊面板的案例

          1. 完成模版部分的定義:
           <!-- 沒有實際意義, 不會渲染到頁面上 -->
           <template id="collapse_tmpl">
             <div class="zf-collapse">
               <slot></slot>
             </div>
           </template>
           <template id="collapse_item_tmpl">
             <div class="zf-collapse-item">
               <div class="title"></div>
               <div class="content">
                 <slot></slot>
               </div>
             </div>
           </template>
          1. 創(chuàng)建組件
          class Collapse extends HTMLElement {
           constructor() {
             super();
             const shadow = this.attachShadow({
               mode'open'
             });
             const tmpl = document.getElementById('collapse_tmpl');
             let cloneTemplate = tmpl.content.cloneNode(true);
             let style = document.createElement('style');
             // :host 代表的是影子的根元素
             style.textContent = `
                     :host{
                         display:flex;
                         border:3px solid #ebebeb;
                         border-radius:5px;
                         width:100%;
                     }
                     .zf-collapse{
                         width:100%;
                     }
                 `

             shadow.appendChild(style);
             shadow.appendChild(cloneTemplate);

             let slot = shadow.querySelector('slot'); // 監(jiān)控slot變化
             slot.addEventListener('slotchange', (e) => {
               this.slotList = e.target.assignedElements();
               this.render();
             })
           }
           static get observedAttributes() { // 監(jiān)控屬性的變化
             return ['active']
           }
           // update
           attributeChangedCallback(key, oldVal, newVal) {
             if (key == 'active') {
               this.activeList = JSON.parse(newVal);
               this.render();
             }
           }
           render() {
             if (this.slotList && this.activeList) {
               [...this.slotList].forEach(child => {
                 child.setAttribute('active'JSON.stringify(this.activeList))
               });
             }
           }

          }
          export default Collapse
          class CollapseItem extends HTMLElement {
           constructor() {
             super();
             let shadow = this.attachShadow({
               mode'open'
             });
             let tmpl = document.getElementById('collapse_item_tmpl');
             let cloneTemplate = tmpl.content.cloneNode(true);
             let style = document.createElement('style');
             this.isShow = true// 標識自己是否需要顯示

             style.textContent = `
                     :host{
                         width:100%;
                     }
                     .title{
                         background:#f1f1f1;
                         line-height:35px;
                         height:35px;
                     }
                     .content{
                         font-size:14px;
                     }
                 `


             shadow.appendChild(style)
             shadow.appendChild(cloneTemplate);
             this.titleEle = shadow.querySelector('.title');

             this.titleEle.addEventListener('click', () => {
               // 如果將結(jié)果傳遞給父親  組件通信?
               document.querySelector('zf-collapse').dispatchEvent(new CustomEvent('changeName', {
                 detail: {
                   namethis.getAttribute('name'),
                   isShowthis.isShow
                 }
               }))
             })
           }

           static get observedAttributes() { // 監(jiān)控屬性的變化
             return ['active''title''name']
           }
           // update
           attributeChangedCallback(key, oldVal, newVal) {
             switch (key) {
               case 'active':
                 this.activeList = JSON.parse(newVal); // 子組件接受父組件的數(shù)據(jù)
                 break;
               case 'title':
                 this.titleEle.innerHTML = newVal; // 接受到title屬性 作為dom的title
                 break;
               case 'name':
                 this.name = newVal
                 break;
             }
             let name = this.name;
             if (this.activeList && name) {
               this.isShow = this.activeList.includes(name);
               this.shadowRoot.querySelector('.content').style.display = this.isShow ? 'block' : 'none'
             }
           }
          }
          export default CollapseItem
          1. 頁面使用:
          <!DOCTYPE html>
          <html lang="en">

          <head>
           <meta charset="UTF-8">
           <meta name="viewport" content="width=device-width, initial-scale=1.0">
           <title>Document</title>
          </head>

          <body>
           <zf-collapse>
             <zf-collapse-item title="Node" name="1">
               <div>nodejs welcome</div>
             </zf-collapse-item>
             <zf-collapse-item title="react" name="2">
               <div>react welcome</div>
             </zf-collapse-item>
             <zf-collapse-item title="vue" name="3">
               <div>vue welcome</div>
             </zf-collapse-item>
           </zf-collapse>

           <!-- 沒有實際意義, 不會渲染到頁面上 -->
           <template id="collapse_tmpl">
             <div class="zf-collapse">
               <slot></slot>
             </div>
           </template>
           <template id="collapse_item_tmpl">
             <div class="zf-collapse-item">
               <div class="title"></div>
               <div class="content">
                 <slot></slot>
               </div>
             </div>
           </template>
           <!-- vite 實現(xiàn)原理 就依賴于 type="module" -->
           <script src="./index1.js" type="module"></script>
          </body>
          </html>

          參考資料:

          • web Components MDN
          • 案例學習:https://github.com/mdn/web-components-examples
          Node 社群


          我組建了一個氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對Node.js學習感興趣的話(后續(xù)有計劃也可以),我們可以一起進行Node.js相關(guān)的交流、學習、共建。下方加 考拉 好友回復「Node」即可。


             “分享、點贊在看” 支持一波??

          瀏覽 45
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  99精品成人免费毛片无码 | 新版天堂资源中文网 | gogo大胆无码无码免费衩频 | 国产AV大片 | 免费无遮挡 视频网乱码 |