Web componentd組件內部事件回調及痛點剖析

寫在前面

最近致力於研究 Web components(以下簡稱WC),並且也初有成效的拿到瞭一定的結果,但今天想回過頭來重新審視一下 WC。

WC 到底是什麼?

簡單的講,Web Component 就是把組件封裝成 html 標簽的形式,並且在使用時不需要寫額外的 js 代碼。

組件是前端的發展方向,拋開周邊技術生態,單純看 React 和 Vue 都是組件框架。因此,WC 可以視為原生標簽的拓展/延伸,說到底,它依舊是一個標簽!

類似 <video></video> 標簽,相比於原生標簽,它多瞭更為豐富的樣式和可操作屬性。

谷歌公司由於掌握瞭 Chrome 瀏覽器,一直在推動瀏覽器的原生組件,即 Web Components API。

相比第三方框架,原生組件簡單直接,符合直覺,不用加載任何外部模塊,代碼量小。貌似一切完美,似乎大有可以用來替換React、Vue之類的趨勢。

目前存在的缺陷

與其它 web 框架一起使用存在一些小問題,會給開發體驗上造成一些困擾。

1、組件內部事件的回調

比如,一個彈窗組件(<my-dialog></my-dialog>)中的確定按鈕,那麼它的事件是如何觸發的呢?

class myDialog extends HTMLElement {
  // ...
  connectedCallback() {
    const shadowRoot = this.attachShadow({ mode: 'open' });
    shadowRoot.innerHTML = `
      <div class="dialog">
        <div class="dialog-content">
          <div class="dialog-body">
            彈窗內容
          </div>
 
          <button id="okBtn">確定</button>
        </div>
      </div>
    `;
     shadowRoot.querySelector('#okBtn').addEventListener('click', () => {
      // 組件內部定義事件
      this.dispatchEvent(new CustomEvent('okBtnFn'));
    });
  }
} 
customElements.define('my-dialog', myDialog);

現在方案是 custom element 內部自定義事件 new CustomEvent(),外部用 addEventListener監聽。這樣的寫法是很醜陋的,仿佛又回到瞭原生 JS 寫應用的時代。

<my-dialog></my-dialog> 
<script>
  export default {
    created() {
      document.addEventListener('okBtnFn', function(){
        // 點擊彈窗按鈕,觸發回調事件
      });
    }
  }
</script>

2、組件樣式覆蓋

對於開發者來說,難免會遇到需要調整組件內部樣式的時候。無論你是使用antdvant還是使用其它組件庫,但 WC 的 CSS 防污染機制導致你很難修改內部樣式。這需要你付出一些代價來變相的修改內部樣式

3、組件內部資源相對路徑問題

就目前來說,任何直接基於 Custom Element v1, Template 和 HTML Import 的組件都無法做到完全資源獨立 —— 在不知道使用方環境且不給使用方增加額外限制的情況下使用內部封裝的任何資源文件。比如如果你有一個自定義 icon 組件:

class MyIcon extends HTMLElement {
    static get observedAttributes() { return ['name','size','color'] }
    constructor() {
        super();
        const shadowRoot = this.attachShadow({ mode: 'open' });
        shadowRoot.innerHTML = `
            <svg class="icon" id="icon" aria-hidden="true" viewBox="0 0 1024 1024">
                <use id="use"></use>
            </svg>        
    } 
    attributeChangedCallback (name, oldValue, newValue) {
        if( name == 'name' && this.shadowRoot){
            // 如果使用的項目中,根目錄沒有 icon.svg 文件,那就 gg
            this.use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', `./icon.svg#icon-${newValue}`);
        }
    }
}
customElements.define('my-icon', MyIcon);

如果使用的項目中,根目錄沒有 icon.svg 文件,那就 gg。如果你在這裡使用 cdn 路徑,就會出現跨域問題。

4、form表單類組件 value 獲取問題

Shadow DOM 中包含有 <input>、<textarea> 或 <select> 等標簽的 value 不會在 form 表單中自動關聯。

示例代碼:

// web component
class InputAge extends HTMLElement {
  constructor() {
    super();
  }  
// connect component
  connectedCallback() {
    const shadow = this.attachShadow({ mode: 'closed' });
    shadow.innerHTML = `<input type="number" placeholder="age" min="18" max="120" />`;
  }
}
// register component
customElements.define( 'input-age', InputAge );

WC 組件被使用後

<form id="myform">
  <input type="text" name="your-name" placeholder="name" />
  <input-age name="your-age"></input-age>
 
  <button>submit</button>
</form>
 
<script>
 const form = document.getElementById('myform');
 
  form.addEventListener('submit', e => {
    
    e.preventDefault();
    console.log('Submitted data:');
 
    const data = new FormData(form);
    for (let nv of data.entries()) {
      console.log(`  ${ nv[0] }: ${ nv[1] }`);
    }
 
  });
</script>

提交的時候無法獲取 input-age 的 value。當然會有解決方案,但會很復雜。

5、其它

此外,缺少數據綁定和狀態管理也是 WC 存在的缺陷,此處不再贅述。

寫在後面

WC 指在豐富 HTML 的 DOM 特性,讓 HTML 擁有更強大的復用能力

WC 可以直接當做原生標簽,在任何前端框架和無框架中運行

結合當下的主流技術棧來說,WC 當前主要問題在於復雜的組件中,數據通信和事件傳遞存在一定使用成本

兼容問題,比如可以覆蓋內部樣式的 :part 方法

以上就是Web componentd組件內部事件回調及痛點剖析的詳細內容,更多關於Web componentd事件回調及痛點的資料請關註WalkonNet其它相關文章!

推薦閱讀: