Web Component 开发入门:创建可复用的自定义 HTML 元素

Web Component 允许你创建自定义的 HTML 元素,它们样式封装、行为独立、可在任何项目中使用——无论项目使用 React、Vue 还是原生 HTML。

核心技术

Web Component 由三项核心技术组成:

  1. Custom Elements — 定义新的 HTML 元素
  2. Shadow DOM — 样式和行为封装
  3. HTML Templates — 定义可复用的 HTML 结构

创建一个 Web Component

// 自定义按钮组件
class MyButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  static get observedAttributes() {
    return ['variant', 'disabled'];
  }

  connectedCallback() {
    this.render();
  }

  attributeChangedCallback() {
    this.render();
  }

  render() {
    const variant = this.getAttribute('variant') || 'primary';
    const disabled = this.hasAttribute('disabled') ? 'disabled' : '';

    this.shadowRoot.innerHTML = `
      <style>
        button {
          padding: 10px 20px;
          border: none;
          border-radius: 6px;
          cursor: pointer;
          font-size: 16px;
        }
        button.primary {
          background: #4F46E5;
          color: white;
        }
        button.primary:hover {
          background: #4338CA;
        }
        button.outline {
          background: transparent;
          border: 2px solid #4F46E5;
          color: #4F46E5;
        }
        button[disabled] {
          opacity: 0.6;
          cursor: not-allowed;
        }
      </style>
      <button class="${variant}" ${disabled}>
        <slot></slot>
      </button>
    `;

    // 事件处理
    this.shadowRoot.querySelector('button')
      .addEventListener('click', () => {
        this.dispatchEvent(new CustomEvent('my-click', {
          detail: { id: this.id }
        }));
      });
  }
}

// 注册自定义元素
customElements.define('my-button', MyButton);

使用自定义元素

<my-button variant="primary" id="submit-btn">提交</my-button>
<my-button variant="outline">取消</my-button>
<my-button disabled>已禁用</my-button>

<script>
  document.querySelector('#submit-btn')
    .addEventListener('my-click', (e) => {
      console.log('Button clicked:', e.detail.id);
    });
</script>

更复杂的例子:计数器组件

class Counter extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.count = 0;
  }

  connectedCallback() {
    this.render();
    this.shadowRoot.querySelector('#inc')
      .addEventListener('click', () => this.update(1));
    this.shadowRoot.querySelector('#dec')
      .addEventListener('click', () => this.update(-1));
  }

  update(amount) {
    this.count += amount;
    this.shadowRoot.querySelector('#value').textContent = this.count;
    this.dispatchEvent(new CustomEvent('count-change', {
      detail: { count: this.count }
    }));
  }

  render() {
    this.shadowRoot.innerHTML = `
      <style>
        :host { display: inline-flex; align-items: center; gap: 12px; }
        button {
          width: 36px; height: 36px;
          border: 1px solid #ccc;
          border-radius: 50%;
          background: white;
          cursor: pointer;
          font-size: 18px;
        }
        span { min-width: 30px; text-align: center; font-size: 20px; }
      </style>
      <button id="dec">-</button>
      <span id="value">0</span>
      <button id="inc">+</button>
    `;
  }
}

customElements.define('my-counter', Counter);
<my-counter></my-counter>
<my-counter></my-counter>

Web Component 的优势

特性 说明
框架无关 在任何项目中都能使用
样式隔离 Shadow DOM 确保样式不冲突
原生支持 无需编译、无需 polyfill(现代浏览器)
可复用 一次编写,处处使用
封装 HTML、CSS、JS 打包在一个文件中

16IDC 观察

Web Component 是最佳的前端组件封装方式——不依赖任何框架,可以在任何项目中复用。对于设计系统(Design System)和 UI 组件库来说,Web Component 是比框架特定组件更长寿的选择。如果你的团队需要维护多个不同技术栈的项目,Web Component 是一次编写、处处运行的最佳实践。