使用 Web Components 开发可复用的 UI 组件

什么是 Web Components?

Web Components 是一种用于开发可重用组件的技术,它由三个主要规范组成:Custom Elements、Shadow DOM 和 HTML Templates。这些规范允许我们创建自定义 HTML 元素并在其中封装样式和行为。

为什么要使用 Web Components?

Web Components 帮助我们解决了两个主要问题:可复用性和封装性。

可复用性:Web Components 允许我们创建通用的 UI 组件,这些组件可以在不同的项目、不同的页面和不同的框架中重复使用,避免了代码复制和粘贴的问题。

封装性:Web Components 可以封装复杂的行为和样式,并通过一个简单的自定义标签来使用它们。这样我们就可以将一些复杂的 UI 组件封装成一个简单的标签,让其他开发者可以更容易地使用它们。

如何开发 Web Components?

步骤一:创建 Custom Element

Custom Element 是 Web Components 中最重要的概念,它允许我们创建自定义的 HTML 元素。

class MyElement extends HTMLElement {
  constructor() {
    super();
  }
}
customElements.define('my-element', MyElement);

上面的代码定义了一个名为 my-element 的 Custom Element。

步骤二:添加 Shadow DOM

Shadow DOM 是 Web Components 中用于封装样式和行为的技术,它允许我们创建一个“影子 DOM”,独立于文档的主要 DOM。

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

    const style = document.createElement('style');
    style.textContent = `
      :host {
        display: block;
      }
    `;
    shadow.appendChild(style);

    const content = document.createElement('div');
    content.textContent = 'Hello, World!';
    shadow.appendChild(content);
  }
}
customElements.define('my-element', MyElement);

上面的代码添加了一个 Shadow DOM,并在其中包含了一个样式和一个 div 元素。

步骤三:使用 HTML Templates

HTML Templates 允许我们将一段 HTML 代码包装起来,并在需要时将其实例化。

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

    const template = document.createElement('template');
    template.innerHTML = `
      <style>
        :host {
          display: block;
        }
      </style>
      <div>
        <slot name="header"></slot>
        <div id="content">
          <slot></slot>
        </div>
        <slot name="footer"></slot>
      </div>
    `;
    shadow.appendChild(template.content.cloneNode(true));
  }
}
customElements.define('my-element', MyElement);

上面的代码使用了一个 HTML Template,并在其中引用了三个插槽。

步骤四:使用插槽

插槽允许我们在 Web Components 中定义可变的区域,使其可以根据需要进行替换。

<my-element>
  <div slot="header">Hello</div>
  <div>World</div>
  <button slot="footer">Click me</button>
</my-element>

上面的代码在 my-element 中使用了三个插槽,分别为 headerdefaultfooter

示例:实现一个可复用的弹框组件

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

    const template = document.createElement('template');
    template.innerHTML = `
      <style>
        :host {
          position: fixed;
          top: 0;
          left: 0;
          right: 0;
          bottom: 0;
          z-index: 99999;
          display: flex;
          align-items: center;
          justify-content: center;
        }
        #modal-container {
          border-radius: 3px;
          box-shadow: 0 0 10px rgba(0, 0, 0, .1);
          background-color: #fff;
          max-width: 800px;
          max-height: 600px;
          overflow: auto;
          display: flex;
          flex-direction: column;
        }
        #header {
          padding: 10px;
          border-bottom: 1px solid #eee;
        }
        #content {
          padding: 10px;
          flex: 1;
        }
        #footer {
          padding: 10px;
          border-top: 1px solid #eee;
          display: flex;
          justify-content: flex-end;
        }
      </style>
      <div id="modal-container">
        <div id="header">
          <slot name="header"></slot>
        </div>
        <div id="content">
          <slot></slot>
        </div>
        <div id="footer">
          <slot name="footer"></slot>
          <button id="cancel">Cancel</button>
          <button id="confirm">Confirm</button>
        </div>
      </div>
    `;
    shadow.appendChild(template.content.cloneNode(true));

    const cancelButton = shadow.querySelector('#cancel');
    cancelButton.addEventListener('click', () => {
      this.close();
    });

    const confirmButton = shadow.querySelector('#confirm');
    confirmButton.addEventListener('click', () => {
      this.done();
    });
  }

  connectedCallback() {
    const modal = document.createElement('div');
    modal.id = 'backdrop';
    modal.addEventListener('click', () => {
      this.close();
    });
    document.body.appendChild(modal);
  }

  disconnectedCallback() {
    const modal = document.querySelector('#backdrop');
    if (modal) {
      modal.removeEventListener('click', () => {
        this.close();
      });
      modal.remove();
    }
  }

  open() {
    const modal = this.shadowRoot.querySelector('#modal-container');
    modal.style.display = 'block';
  }

  close() {
    const modal = this.shadowRoot.querySelector('#modal-container');
    modal.style.display = 'none';
  }

  done() {
    const event = new CustomEvent('done');
    this.dispatchEvent(event);
    this.close();
  }
}
customElements.define('modal-dialog', ModalDialog);

上面的代码定义了一个名为 modal-dialog 的 Custom Element,它有一个 open 方法用于打开弹框,一个 close 方法用于关闭弹框,一个 done 方法用于完成操作并关闭弹框,以及三个插槽:headerdefaultfooter

<modal-dialog>
  <div slot="header">Hello</div>
  <div>World</div>
  <button slot="footer">Cancel</button>
  <button slot="footer">Save</button>
</modal-dialog>

上面的代码使用了 modal-dialog 组件,并在其中使用了三个插槽,并为 footer 插槽指定了两个按钮。

总结

Web Components 是一种非常强大的技术,在 UI 组件的重用性和封装性方面具有很大的优势。但是它仍然是一个相对较新的技术,需要浏览器和框架的支持。在开发时,我们建议使用现有的 Web Components 库或框架,例如 Polymer 或 Stencil,以快速地创建可复用的 UI 组件。

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65b01e9cadd4f0e0ff98a843