Web Components:原生组件化开发新趋势,零基础也能上手
大家好,我是老张,一名985毕业的全栈工程师,平时在掘金写了不少前端入门教程。最近有位刚转行的朋友问我:“现在学前端,是不是必须用 React 或 Vue?”我笑了笑,说:“其实浏览器原生就支持组件化开发,只是很多人不知道。”
这让我想起自己刚入行时,也以为组件化是框架的“专利”。直到后来在项目中遇到性能瓶颈、打包体积过大等问题,才重新关注到 Web Components 这个被低估的原生能力。今天这篇教程,就是想带完全零基础的同学,从零开始掌握 Web Components —— 无需任何框架,就能写出可复用、可维护的组件。
为什么你应该了解 Web Components?
简单来说,Web Components 是一套浏览器原生支持的组件化技术标准,它让你像搭积木一样,把 UI 拆分成独立的小模块(比如一个按钮、一个卡片、一个日历),然后在任何页面中重复使用。
你可能在简历上看到过这个词,但觉得“太底层”不敢碰。其实不然!它比你想象的更简单,而且:
- 不依赖任何框架:React、Vue 都要编译,而 Web Components 直接运行在浏览器里
- 天然隔离:每个组件自带样式和逻辑,互不干扰(靠 Shadow DOM 实现)
- 高度可复用:写一次,到处用,甚至能跨项目、跨团队共享
- 未来趋势:Google、GitHub、Salesforce 等大厂都在用,OpenCode 开源项目中也越来越多见
我当初学的时候,就是被“原生”两个字吸引——既然浏览器自己支持,何必再套一层框架?
环境准备:5分钟搞定开发环境
好消息是:你不需要安装任何构建工具!Web Components 是原生 API,现代浏览器(Chrome、Edge、Firefox、Safari)都支持。
最小开发环境清单
| 工具 | 说明 |
|---|---|
| 浏览器 | 推荐 Chrome 最新版 |
| 代码编辑器 | VS Code、Sublime、甚至记事本都行 |
| 本地服务器(可选) | 用于避免 file:// 协议的 CORS 限制 |
💡 新手注意:直接双击 HTML 文件打开可能会报错(因为浏览器安全策略)。建议用 VS Code 安装 Live Server 插件,右键“Open with Live Server”即可启动本地服务。
验证环境是否 OK
新建一个 index.html,粘贴以下代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Web Components 测试</title>
</head>
<body>
<my-greeting></my-greeting>
<script>
class MyGreeting extends HTMLElement {
connectedCallback() {
this.innerHTML = '<h1>Hello, Web Components!</h1>';
}
}
customElements.define('my-greeting', MyGreeting);
</script>
</body>
</html>
用 Live Server 打开,如果看到 “Hello, Web Components!”,恭喜你,环境搞定!
核心三件套:Custom Elements + Shadow DOM + HTML Templates
Web Components 由三个核心技术组成,我们逐个拆解。
1. Custom Elements(自定义元素)
这是最核心的部分——让你定义自己的 HTML 标签。
规则很简单:
- 标签名必须包含连字符
-(如my-button,不能叫button) - 通过
customElements.define()注册
// 定义一个组件类
class MyButton extends HTMLElement {
constructor() {
super(); // 必须调用 super()
}
// 元素被插入 DOM 时触发
connectedCallback() {
this.innerHTML = `<button>点我</button>`;
}
}
// 注册组件
customElements.define('my-button', MyButton);
现在你就可以在 HTML 中直接写 <my-button></my-button> 了!
📌 避坑指南:很多人忘了调用
super(),导致报错。记住:只要继承HTMLElement,构造函数第一行必须是super()。
2. Shadow DOM(影子 DOM)
解决了“样式污染”问题。普通组件的 CSS 会影响全局,但 Shadow DOM 让组件拥有私有作用域。
改造上面的按钮:
class MyButton extends HTMLElement {
constructor() {
super();
// 创建 Shadow DOM
const shadow = this.attachShadow({ mode: 'open' });
// 内部结构
shadow.innerHTML = `
<style>
button {
background: #4CAF50;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #45a049;
}
</style>
<button>点我</button>
`;
}
}
现在,这个按钮的样式只在组件内部生效,不会影响页面其他按钮。
🔍 mode: 'open' vs 'closed'
open:外部可通过element.shadowRoot访问内部closed:完全封闭,外部无法访问(但实际很少用)
3. HTML Templates(模板)
虽然不是必须,但 <template> 标签能让你预定义 HTML 结构,提升可读性。
<template id="my-card-template">
<style>
.card { border: 1px solid #ccc; padding: 16px; }
.title { font-weight: bold; }
</style>
<div class="card">
<div class="title"></div>
<div class="content"></div>
</div>
</template>
<script>
class MyCard extends HTMLElement {
constructor() {
super();
const template = document.getElementById('my-card-template');
const shadow = this.attachShadow({ mode: 'open' });
shadow.appendChild(template.content.cloneNode(true));
}
connectedCallback() {
// 从属性获取数据
const title = this.getAttribute('title') || '默认标题';
const content = this.getAttribute('content') || '默认内容';
this.shadowRoot.querySelector('.title').textContent = title;
this.shadowRoot.querySelector('.content').textContent = content;
}
}
customElements.define('my-card', MyCard);
</script>
使用方式:
<my-card title="欢迎" content="这是卡片内容"></my-card>
✅ 优点:HTML 结构和 JS 逻辑分离,代码更清晰。
实战项目:做一个可复用的“待办事项”组件
现在,我们综合运用所学,做一个完整的 <todo-list> 组件。
步骤 1:规划功能
- 显示任务列表
- 输入框 + “添加”按钮
- 点击任务可标记完成
- 数据通过属性传入(JSON 字符串)
步骤 2:编写代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Todo List 组件</title>
</head>
<body>
<todo-list tasks='[{"text":"学习Web Components","done":false}]'></todo-list>
<script>
class TodoList extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
.container { max-width: 400px; margin: 20px auto; font-family: Arial; }
input, button { padding: 8px; margin: 5px; }
ul { list-style: none; padding: 0; }
li { padding: 8px; border-bottom: 1px solid #eee; cursor: pointer; }
.done { text-decoration: line-through; color: #999; }
</style>
<div class="container">
<input type="text" id="new-task" placeholder="输入新任务">
<button id="add-btn">添加</button>
<ul id="task-list"></ul>
</div>
`;
}
connectedCallback() {
this.tasks = JSON.parse(this.getAttribute('tasks') || '[]');
this.render();
this.bindEvents();
}
render() {
const list = this.shadowRoot.querySelector('#task-list');
list.innerHTML = '';
this.tasks.forEach((task, index) => {
const li = document.createElement('li');
li.textContent = task.text;
if (task.done) li.classList.add('done');
li.dataset.index = index;
list.appendChild(li);
});
}
bindEvents() {
const input = this.shadowRoot.querySelector('#new-task');
const btn = this.shadowRoot.querySelector('#add-btn');
const list = this.shadowRoot.querySelector('#task-list');
btn.addEventListener('click', () => {
if (input.value.trim()) {
this.tasks.push({ text: input.value, done: false });
input.value = '';
this.render();
}
});
list.addEventListener('click', (e) => {
if (e.target.tagName === 'LI') {
const index = e.target.dataset.index;
this.tasks[index].done = !this.tasks[index].done;
this.render();
}
});
}
}
customExtensions.define('todo-list', TodoList); // 注意:这里故意写错!
</script>
</body>
</html>
⚠️ 故意埋了个 Bug:最后一行应该是
customElements.define,不是customExtensions。这是新手常犯的拼写错误!
修复后,刷新页面,你就能看到一个可交互的待办列表了!
新手常见问题 & 解答
Q1:组件之间怎么通信?
- 父传子:通过 HTML 属性(如
<my-comp name="张三">) - 子传父:派发自定义事件
// 子组件中
this.dispatchEvent(new CustomEvent('task-added', {
detail: { task: '新任务' },
bubbles: true // 允许冒泡
}));
// 父页面监听
document.querySelector('todo-list').addEventListener('task-added', (e) => {
console.log('收到新任务:', e.detail.task);
});
Q2:能用 npm 包吗?
可以!虽然 Web Components 本身是原生的,但你可以:
- 用 Lit、Stencil 等轻量库简化开发
- 将 Web Components 发布到 npm,供他人使用
Q3:和 React/Vue 冲突吗?
完全不冲突!你可以在 React 项目中直接使用 Web Components,反之亦然。很多公司用它做微前端的边界组件。
学习建议:下一步怎么走?
动手改造现有项目
把你页面中的重复 UI(如按钮、卡片、弹窗)抽成 Web Components。阅读经典书籍
推荐《Web Components in Action》(Manning 出版),讲解深入浅出。参与开源
在 GitHub 搜索web-components,看看 OpenCode 项目是如何组织大型组件库的。善用 AI 辅助
遇到问题可以用 Kimi、Copilot 等工具生成示例代码,但务必理解原理。更新简历
掌握 Web Components 是加分项,尤其在强调“原生能力”或“微前端”的岗位中。
结语
Web Components 不是银弹,但它给了我们一种回归原生、轻量高效的开发思路。我当初学它,是为了减少对框架的依赖;现在用它,是为了构建更灵活的系统架构。
希望这篇教程能帮你迈出第一步。记住:所有复杂的概念,拆开看都不难。多写几遍代码,你就会发现,组件化开发,原来如此自然。
作者:老张,掘金专栏《前端入门不迷路》主理人
声明:本文代码均可直接运行,欢迎 fork 到你的 OpenCode 仓库!

评论 0