Web Components:原生组件化开发,你真的不需要框架吗?

邓建国
2026-05-24 04:00
阅读 3290

大家好,我是211高校计算机专业的研二学生,平时喜欢在 GitHub 上写技术博客,帮助刚入门的学弟学妹少走弯路。最近有不少初学者问我:“现在前端框架这么多,还有必要学 Web Components 吗?”我当初学的时候也这么疑惑过——直到我发现,Web Components 是浏览器原生支持的组件化方案,不依赖 Vue、React 等任何框架,却能实现同样的封装与复用能力。

今天这篇教程,就带零基础的你,从零开始掌握 Web Components 的核心用法,并动手做一个可复用的“点赞按钮”组件。全程不用 npm、不用构建工具,只用一个 HTML 文件就能跑起来!


一、什么是 Web Components?为什么值得学?

Web Components 是一套由 W3C 标准定义的浏览器原生 API,包含三个核心技术:

  1. Custom Elements(自定义元素):让你创建自己的 HTML 标签,比如 <my-button>。
  2. Shadow DOM(影子 DOM):提供样式和 DOM 的封装,避免外部 CSS 干扰组件内部。
  3. HTML Templates(模板):用 <template> 标签预定义 HTML 结构,按需渲染。

✅ 优势:无需打包、无运行时开销、跨框架兼容(Vue/React/Angular 都能用!)
❌ 局限:IE 不支持(但现代项目基本不用 IE 了)

我当初第一次看到 <custom-card></custom-card> 能直接在页面上工作时,简直惊呆了——原来浏览器自己就能做组件化!


二、环境准备:5 分钟快速上手

好消息是:你不需要安装 Node.js、Webpack 或任何构建工具!

只需:

  • 一个现代浏览器(Chrome / Edge / Firefox / Safari)
  • 一个文本编辑器(VS Code、记事本都行)
  • 本地打开 HTML 文件(或通过 live-server 启动简易服务)

🔧 推荐工具:如果你习惯命令行,可以用 npx live-server 快速启动本地服务(需先安装 Node.js)。但直接双击 HTML 文件也能运行大部分功能。


三、三大核心概念详解(附代码)

1. 自定义元素(Custom Elements)

通过 customElements.define() 注册一个新标签。

<!-- index.html -->
<script>
class MyButton extends HTMLElement {
  constructor() {
    super();
    this.innerHTML = '<button>点我</button>';
  }
}

// 注册标签名为 my-button
customElements.define('my-button', MyButton);
</script>

<my-button></my-button>

💡 注意:自定义标签名必须包含连字符 -,这是浏览器区分原生标签和自定义标签的规则。


2. Shadow DOM:隔离样式与结构

上面的例子有个问题:如果外部有 button { color: red; },按钮颜色会被污染。用 Shadow DOM 解决:

class MyButton extends HTMLElement {
  constructor() {
    super();
    // 创建 Shadow Root
    const shadow = this.attachShadow({ mode: 'open' });
    
    // 定义内部结构
    shadow.innerHTML = `
      <style>
        button { 
          background: #4CAF50; 
          color: white; 
          border: none;
          padding: 8px 16px;
          border-radius: 4px;
        }
        button:hover { opacity: 0.8; }
      </style>
      <button>👍 点赞</button>
    `;
  }
}

现在,外部样式完全无法影响这个按钮!这就是 Shadow DOM 的封装性。


3. HTML Template:预定义结构

对于复杂组件,直接写字符串容易出错。可以用 <template> 提前写好结构:

<template id="my-button-template">
  <style>
    button { background: #2196F3; color: white; }
  </style>
  <button>❤️ 喜欢</button>
</template>

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

✅ cloneNode(true) 表示深拷贝模板内容,避免多次使用时互相干扰。


四、实战:做一个带计数功能的点赞组件

现在,我们整合所有知识,做一个真正的组件:点击按钮,数字+1。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Web Components 实战</title>
</head>
<body>
  <like-button></like-button>
  <like-button></like-button>

  <template id="like-button-template">
    <style>
      .container {
        display: inline-flex;
        align-items: center;
        gap: 8px;
        font-family: Arial;
      }
      button {
        background: #ff4757;
        color: white;
        border: none;
        width: 32px;
        height: 32px;
        border-radius: 50%;
        cursor: pointer;
      }
      span { font-weight: bold; }
    </style>
    <div class="container">
      <button>👍</button>
      <span>0</span>
    </div>
  </template>

  <script>
    class LikeButton extends HTMLElement {
      constructor() {
        super();
        const template = document.getElementById('like-button-template');
        const content = template.content.cloneNode(true);
        this.shadow = this.attachShadow({ mode: 'open' });
        this.shadow.appendChild(content);

        // 获取按钮和计数器
        this.button = this.shadow.querySelector('button');
        this.countSpan = this.shadow.querySelector('span');

        // 绑定点击事件
        this.button.addEventListener('click', () => {
          const count = parseInt(this.countSpan.textContent) + 1;
          this.countSpan.textContent = count;
        });
      }
    }

    customElements.define('like-button', LikeButton);
  </script>
</body>
</html>

保存为 index.html,双击打开,试试点击两个按钮——它们各自独立计数!这就是组件化的魅力。


五、新手常见问题解答(避坑指南)

问题 原因 解决方案
自定义标签不显示 忘记调用 customElements.define() 检查是否注册了类
样式没生效 没用 Shadow DOM,被全局样式覆盖 使用 attachShadow 封装
无法获取子元素 在 connectedCallback 前操作 DOM 把初始化逻辑放在 connectedCallback 中更安全
组件之间状态共享难 Web Components 默认无状态管理 可结合原生 dispatchEvent 实现通信

📌 重要提示:生命周期方法如 connectedCallback(插入 DOM 时触发)比 constructor 更适合绑定事件或获取属性。


六、学习建议与下一步

Web Components 虽然强大,但不适合构建大型 SPA 应用(缺少状态管理、路由等)。但它非常适合:

  • 封装 UI 原子组件(按钮、卡片、弹窗)
  • 开发跨框架的微前端模块
  • 构建轻量级嵌入式小工具(如评论插件)

推荐学习路径:

  1. 巩固基础:熟练掌握 Custom Elements + Shadow DOM
  2. 进阶实践:学习使用 attributeChangedCallback 监听属性变化
  3. 工具辅助:尝试 Lit(Google 出品的轻量库,简化 Web Components 开发)
  4. 开源参与:在 GitHub 上搜索 web-components,阅读优秀项目源码

🔗 我在 GitHub 上维护了一个入门示例仓库,包含本文所有代码,欢迎 Star:github.com/yourname/web-components-demo(替换为你的实际链接)

最后,别忘了试试 Aider —— 这是一个新兴的 AI 编程助手,支持直接理解并生成 Web Components 代码。你可以对它说:“帮我写一个带输入框的搜索组件”,它会输出完整的 Custom Element 代码,非常适合初学者快速验证想法。


结语:Web Components 是浏览器给我们的“礼物”,它让我们回归原生,理解组件化的本质。即使你未来主要用 Vue 或 React,掌握它也能让你成为更底层的开发者。希望这篇教程能帮你迈出第一步!

有问题欢迎在评论区留言,我会一一回复。下期我们聊聊如何用 Lit 让 Web Components 开发更高效!

评论 0

最热最新
暂无评论
邓建国Lv.1
0
影响力
0
文章
0
粉丝