Web Components:原生组件化开发新趋势
大家好!我是开源项目维护者,也是一名前端讲师。在多年参与和维护各种前端项目的过程中,我深刻体会到“组件化”对现代 Web 开发的重要性。你可能听说过 React、Vue 这些热门框架,但今天我想和你聊聊一个不需要任何框架就能实现组件化的方式——Web Components(Web 组件)。
我当初学的时候,也曾疑惑:“为什么不用 React 就不能写组件?” 后来才发现,其实浏览器早就提供了原生支持组件的能力!这篇文章就是想用最通俗的语言,带你从零开始掌握 Web Components,无论你有没有编程基础,都能跟着一步步上手。
什么是 Web Components?
简单来说,Web Components 是一套浏览器原生支持的技术标准,让你可以像搭积木一样创建可复用的 HTML 元素。比如,你可以自己定义一个 <my-button> 标签,它自带样式、行为和逻辑,就像 <button> 一样自然。
它的核心优势是:
- 无需依赖框架(如 React、Vue)
- 完全兼容现代浏览器
- 真正封装(样式和逻辑不会污染外部)
- 可跨项目复用
💡 技术分享小贴士:Web Components 不是要取代 React,而是提供了一种更底层、更通用的组件化思路。很多大型项目(如 GitHub、Salesforce)都在生产环境中使用它。
环境准备:5 分钟搭建开发环境
好消息是:你不需要安装任何工具! Web Components 是浏览器原生支持的功能,只需一个文本编辑器和一个现代浏览器(Chrome、Edge、Firefox、Safari 最新版均可)。
步骤如下:
新建一个项目文件夹
比如叫web-components-demo创建一个 HTML 文件
在文件夹里新建index.html,内容如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Web Components 入门</title>
</head>
<body>
<h1>你好,Web Components!</h1>
<!-- 我们的组件将在这里显示 -->
<script src="my-component.js"></script>
</body>
</html>
创建一个 JavaScript 文件
新建my-component.js,留空即可。用浏览器打开
index.html
双击文件或拖入浏览器即可看到效果。
🛠️ 避坑指南:不要用老旧的浏览器(如 IE),Web Components 在 IE 中不被支持。建议使用 Chrome 或 Edge。
核心概念:三大支柱
Web Components 由三个关键技术组成,我们逐个来看:
1. 自定义元素(Custom Elements)
这是最直观的部分——让你能定义自己的 HTML 标签。
比如,我们可以定义一个叫 <hello-world> 的标签:
// my-component.js
class HelloWorld extends HTMLElement {
constructor() {
super(); // 必须调用 super()
this.textContent = '你好,世界!';
}
}
// 注册自定义元素
customElements.define('hello-world', HelloWorld);
然后在 HTML 中使用:
<hello-world></hello-world>
刷新页面,你会看到“你好,世界!”显示出来!
✅ 注意:自定义元素的标签名必须包含连字符
-,比如my-button、user-card,这是为了和原生 HTML 标签区分开。
2. 影子 DOM(Shadow DOM)
这是 Web Components 的“魔法”所在——隔离样式和 DOM 结构。
想象一下:如果你写的组件样式不小心影响了整个网页,那就糟了。影子 DOM 就像给组件加了个“透明盒子”,里面的样式不会跑出去,外面的样式也进不来。
改造上面的例子:
class HelloWorld extends HTMLElement {
constructor() {
super();
// 创建影子 DOM
const shadow = this.attachShadow({ mode: 'open' });
// 创建内部元素
const div = document.createElement('div');
div.textContent = '你好,世界!';
// 添加样式
const style = document.createElement('style');
style.textContent = `
div {
color: blue;
font-size: 24px;
font-weight: bold;
}
`;
// 把元素和样式加入影子 DOM
shadow.appendChild(style);
shadow.appendChild(div);
}
}
customElements.define('hello-world', HelloWorld);
现在,即使你在 HTML 里写 div { color: red; },也不会影响到这个组件里的文字颜色。
🔍 小知识:
mode: 'open'表示可以通过 JavaScript 访问影子 DOM(比如element.shadowRoot)。如果设为'closed',则完全封闭,外部无法访问。
3. HTML 模板(HTML Templates)
当组件结构复杂时,用 document.createElement 会很麻烦。这时可以用 <template> 标签预定义结构。
<!-- 在 index.html 的 <body> 里添加 -->
<template id="hello-template">
<style>
p {
color: green;
font-family: monospace;
}
</style>
<p>来自模板的内容!</p>
</template>
然后在 JS 中使用:
class HelloWorld extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
// 获取模板
const template = document.getElementById('hello-template');
const instance = template.content.cloneNode(true);
shadow.appendChild(instance);
}
}
customElements.define('hello-world', HelloWorld);
💡 提示:
cloneNode(true)表示深度克隆,包括所有子节点。
实战项目:做一个可复用的用户卡片组件
现在,我们来做一个实用的小项目:用户信息卡片。它会显示头像、姓名和简介,并且可以自定义内容。
第一步:设计组件接口
我们希望这样使用它:
<user-card
avatar="https://example.com/avatar.jpg"
name="张三"
bio="热爱代码人生,前端工程师"
></user-card>
第二步:编写组件代码
在 my-component.js 中:
class UserCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
// 获取属性值
const avatar = this.getAttribute('avatar') || 'https://via.placeholder.com/50';
const name = this.getAttribute('name') || '匿名用户';
const bio = this.getAttribute('bio') || '暂无简介';
// 构建 HTML
shadow.innerHTML = `
<style>
.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
max-width: 300px;
font-family: Arial, sans-serif;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.avatar {
width: 50px;
height: 50px;
border-radius: 50%;
vertical-align: middle;
margin-right: 12px;
}
.info {
display: inline-block;
vertical-align: top;
}
.name {
font-size: 18px;
font-weight: bold;
margin: 0;
}
.bio {
color: #666;
margin: 4px 0 0;
}
</style>
<div class="card">
<img class="avatar" src="${avatar}" alt="头像">
<div class="info">
<p class="name">${name}</p>
<p class="bio">${bio}</p>
</div>
</div>
`;
}
}
customElements.define('user-card', UserCard);
第三步:在 HTML 中使用
在 index.html 的 <body> 中添加:
<user-card
avatar="https://via.placeholder.com/50/007bff/ffffff?text=U"
name="李四"
bio="技术分享爱好者,喜欢探索新资源"
></user-card>
<user-card
name="王五"
bio="代码人生的践行者"
></user-card>
刷新页面,你会看到两个漂亮的用户卡片!而且每个卡片的样式完全隔离,互不影响。
🎯 实践建议:试着修改
avatar、name等属性,观察效果变化。这就是组件化的魅力——一次编写,处处使用。
常见问题解答(FAQ)
Q1:Web Components 和 React 组件有什么区别?
| 对比项 | Web Components | React 组件 |
|---|---|---|
| 依赖 | 无(浏览器原生) | 需要 React 库 |
| 学习成本 | 较低(仅需 JS + HTML) | 较高(JSX、状态管理等) |
| 生态 | 较小 | 非常庞大 |
| 性能 | 轻量 | 有虚拟 DOM 开销 |
| 适用场景 | 跨框架共享组件、微前端 | 复杂交互应用 |
📌 建议:如果你要做一个独立的 UI 库(比如按钮、弹窗),Web Components 更合适;如果是大型应用,React/Vue 仍是首选。
Q2:如何监听属性变化?
默认情况下,修改属性不会触发更新。你需要重写 attributeChangedCallback 方法:
class UserCard extends HTMLElement {
static get observedAttributes() {
return ['name', 'bio', 'avatar']; // 声明要监听的属性
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
// 重新渲染
this.render();
}
}
render() {
// 把之前的 innerHTML 逻辑移到这里
}
}
Q3:能传递复杂数据(比如对象)吗?
HTML 属性只能传字符串。如果需要传对象,可以用 JSON 字符串:
<my-component data='{"user": "张三", "age": 25}'></my-component>
然后在 JS 中解析:
const data = JSON.parse(this.getAttribute('data'));
不过更推荐用 JavaScript API 直接设置属性:
const comp = document.querySelector('my-component');
comp.data = { user: '张三', age: 25 };
这需要你在组件类中定义 set data(value) 方法。
学习建议:下一步怎么走?
恭喜你已经掌握了 Web Components 的基础!接下来,你可以:
深入学习
- 阅读 MDN Web Components 文档
- 了解
slot插槽机制(类似 Vue 的 slot)
实战练习
- 尝试做一个
<modal-dialog>弹窗组件 - 制作一个
<code-snippet>代码高亮组件
- 尝试做一个
结合框架使用
很多框架支持嵌入 Web Components。比如在 React 中可以直接使用<user-card>,这在微前端架构中非常有用。关注社区资源
- GitHub 上搜索
web components library - 探索 Lit(Google 出品的轻量级 Web Components 库)
- GitHub 上搜索
结语:开启你的代码人生
Web Components 虽然不如 React 那样声势浩大,但它代表了 Web 平台向标准化、模块化迈出的重要一步。作为开发者,掌握这种原生能力,能让你在技术选型时更加游刃有余。
我当初学的时候,也是从一个简单的 <hello-world> 开始的。今天你迈出的这一步,或许就是未来构建大型系统的基础。技术分享的意义,就在于让更多人少走弯路,快速成长。
希望这篇教程能成为你代码人生中的一个小资源、一个新起点。动手试试吧,你会发现:原生的力量,其实就在你指尖之下。
Happy coding! 🚀

评论 0