一、Web Components 是什么?

如果你写过前端页面,肯定遇到过组件复用的问题。比如一个按钮样式要到处复制粘贴,改起来特别麻烦。Web Components 就是浏览器原生提供的组件化方案,它允许你创建可复用的自定义 HTML 标签,并且自带样式和逻辑封装,不会和其他代码冲突。

简单来说,Web Components 包含三个核心技术:

  1. Custom Elements(自定义元素):让你定义自己的 HTML 标签。
  2. Shadow DOM(影子DOM):把组件的样式和结构隔离起来,避免被外部影响。
  3. HTML Templates(HTML模板):定义可复用的 HTML 片段,不会直接渲染到页面上。

二、从零开始创建第一个 Web Component

下面我们用一个简单的例子来演示如何创建一个 Web Component。假设我们要做一个带计数器的按钮,点击按钮数字会自动增加。

<!-- 技术栈:纯HTML/JavaScript,无框架依赖 -->
<!DOCTYPE html>
<html>
<head>
    <title>Web Components 示例</title>
</head>
<body>
    <!-- 使用自定义标签 -->
    <counter-button></counter-button>

    <script>
        // 1. 定义一个继承自HTMLElement的类
        class CounterButton extends HTMLElement {
            constructor() {
                super();
                this.count = 0;

                // 2. 创建Shadow DOM,隔离组件内部结构
                this.attachShadow({ mode: 'open' });

                // 3. 定义组件的HTML和样式
                this.shadowRoot.innerHTML = `
                    <style>
                        button {
                            padding: 8px 16px;
                            background: #4CAF50;
                            color: white;
                            border: none;
                            border-radius: 4px;
                            cursor: pointer;
                        }
                    </style>
                    <button>点击次数: ${this.count}</button>
                `;

                // 4. 绑定点击事件
                this.shadowRoot.querySelector('button').addEventListener('click', () => {
                    this.count++;
                    this.shadowRoot.querySelector('button').textContent = `点击次数: ${this.count}`;
                });
            }
        }

        // 5. 注册自定义元素
        customElements.define('counter-button', CounterButton);
    </script>
</body>
</html>

代码解析:

  • CounterButton 类继承自 HTMLElement,这是所有 Web Components 的基类。
  • attachShadow({ mode: 'open' }) 创建 Shadow DOM,open 表示外部可以访问它。
  • innerHTML 定义了组件的结构和样式,样式不会影响页面其他部分。
  • customElements.define() 注册自定义标签,这样就能在 HTML 里使用 <counter-button> 了。

三、进阶:使用模板和插槽

如果组件结构复杂,直接写 innerHTML 会很乱。我们可以用 <template> 标签来定义模板,再用 <slot> 实现内容分发。

<!-- 技术栈:纯HTML/JavaScript,无框架依赖 -->
<!DOCTYPE html>
<html>
<head>
    <title>Web Components 进阶示例</title>
</head>
<body>
    <user-card>
        <span slot="name">张三</span>
        <span slot="email">zhangsan@example.com</span>
    </user-card>

    <template id="user-card-template">
        <style>
            .card {
                border: 1px solid #ddd;
                padding: 16px;
                border-radius: 8px;
                max-width: 200px;
            }
            .name {
                font-weight: bold;
                color: #333;
            }
            .email {
                color: #666;
                font-size: 14px;
            }
        </style>
        <div class="card">
            <div class="name"><slot name="name">未提供姓名</slot></div>
            <div class="email"><slot name="email">未提供邮箱</slot></div>
        </div>
    </template>

    <script>
        class UserCard extends HTMLElement {
            constructor() {
                super();
                const template = document.getElementById('user-card-template');
                const content = template.content.cloneNode(true);
                this.attachShadow({ mode: 'open' }).appendChild(content);
            }
        }
        customElements.define('user-card', UserCard);
    </script>
</body>
</html>

代码解析:

  • <template> 定义了一个模板,不会直接渲染,直到被 JavaScript 调用。
  • <slot name="xxx"> 是插槽,外部传入的内容会替换掉默认值。
  • cloneNode(true) 复制模板内容,避免重复使用同一个模板导致冲突。

四、Web Components 的优缺点

优点:

  1. 原生支持:不需要额外框架,浏览器直接运行。
  2. 封装性强:Shadow DOM 让样式和逻辑不会泄露到外部。
  3. 复用方便:自定义标签可以在任何地方使用,甚至跨项目复用。

缺点:

  1. 兼容性问题:旧版本浏览器(如IE)不支持,需要 polyfill。
  2. 生态不完善:相比 React/Vue,社区工具和库较少。
  3. 开发体验一般:没有虚拟 DOM 和响应式数据,复杂逻辑要自己实现。

五、实际应用场景

Web Components 特别适合以下场景:

  1. UI组件库:比如按钮、卡片、弹窗等,一次开发,到处使用。
  2. 微前端架构:不同团队用不同框架开发,用 Web Components 封装各自的模块。
  3. 第三方插件:比如嵌入到其他网站的 SDK,避免样式和 JS 冲突。

六、注意事项

  1. 命名规范:自定义标签必须带短横线(如 <my-component>),不能是单个单词。
  2. 生命周期:可以利用 connectedCallback(组件插入页面时触发)、disconnectedCallback(组件移除时触发)等生命周期函数。
  3. 属性监听:通过 static get observedAttributes()attributeChangedCallback 监听属性变化。

七、总结

Web Components 是浏览器原生组件化方案,适合需要高复用性和隔离性的场景。虽然生态不如主流框架丰富,但在特定需求下非常有用。如果你正在开发一个跨框架的 UI 库,或者希望代码不受其他 JS/CSS 影响,Web Components 值得一试。