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

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

大家好,我是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
粉丝