Web Components:原生组件化开发新趋势
大家好,我是小林,一名211高校的计算机专业研究生,平时喜欢写技术博客帮助刚入门的小伙伴。最近有不少学弟学妹问我:“现在前端框架这么多,React、Vue满天飞,有没有更‘原生’一点的组件化方案?”这让我想起自己当初学前端时也有同样的困惑——是不是非得用 React 才能写组件?
今天这篇教程,我就带大家认识一个不需要任何框架就能实现组件化的技术:Web Components。它是由浏览器原生支持的标准,让你像搭积木一样构建可复用的 UI 元素。无论你是后端同学想快速写点前端界面,还是零基础小白想理解“组件”到底是什么,这篇文章都适合你!
一、Web Components 是什么?能干什么?
简单说,Web Components 就是浏览器自带的“组件系统”。你可以把它想象成一个自定义的 HTML 标签,比如 <my-button>、<user-card>,它内部封装了 HTML 结构、CSS 样式和 JavaScript 行为,用起来就像 <div> 一样简单。
为什么值得关注?
- 无需依赖 React/Vue:纯原生,现代浏览器都支持
- 天然隔离:样式和逻辑不会污染全局(后面会讲“Shadow DOM”)
- 跨框架兼容:你写的 Web Component 可以在 React、Vue 甚至纯 HTML 项目中直接使用
- 对后端友好:如果你是后端开发者,不用学复杂框架也能写出结构清晰的前端界面
我当初第一次看到 Web Components 时,第一反应是:“原来不用 React 也能写组件?!”后来发现,很多大厂(比如 GitHub、Salesforce)都在生产环境用了它。
二、环境准备:5 分钟快速上手
好消息是:你几乎不需要任何配置!
所需工具
| 工具 | 说明 |
|---|---|
| 浏览器 | Chrome、Edge、Firefox、Safari 最新版(都支持) |
| 代码编辑器 | VS Code、Sublime Text 或记事本都行 |
| 本地服务器(可选) | 用 Live Server 插件或 Python 的 http.server |
第一步:创建一个 HTML 文件
新建一个 index.html,内容如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Web Components 入门</title>
</head>
<body>
<h1>我的第一个 Web Component</h1>
<!-- 这里将使用我们自定义的组件 -->
<my-greeting name="小林"></my-greeting>
<script src="greeting.js"></script>
</body>
</html>
第二步:创建组件文件
新建 greeting.js,先留空,下一节我们来填内容。
⚠️ 注意:由于安全限制,不能直接双击打开 HTML 文件!需要用本地服务器运行。
快速启动方法(任选其一):
- VS Code 安装 Live Server 插件,右键“Open with Live Server”
- 终端执行:
python3 -m http.server 8000(Mac/Linux)或python -m http.server 8000(Windows)
三、核心概念:三大支柱
Web Components 由三个核心技术组成,记住这个口诀:C-S-T
| 缩写 | 全称 | 作用 |
|---|---|---|
| C | Custom Elements | 自定义 HTML 标签 |
| S | Shadow DOM | 隔离样式和 DOM |
| T | Template | 定义可复用的 HTML 模板 |
下面我用最通俗的方式解释它们。
1. Custom Elements(自定义元素)
就是让你能写 <my-button> 这种标签。浏览器不认识它?没关系,我们“教”它!
// greeting.js
class MyGreeting extends HTMLElement {
constructor() {
super(); // 必须调用
this.innerHTML = '<p>Hello, 世界!</p>';
}
}
// 注册组件
customElements.define('my-greeting', MyGreeting);
刷新页面,你会看到 “Hello, 世界!”。但名字是写死的,怎么传参数?
2. 使用属性传参
HTML 标签可以加属性,比如 <my-greeting name="张三">。我们在 JS 里读取它:
class MyGreeting extends HTMLElement {
constructor() {
super();
// 获取 name 属性
const name = this.getAttribute('name') || '陌生人';
this.innerHTML = `<p>Hello, ${name}!</p>`;
}
}
customElements.define('my-greeting', MyGreeting);
现在 <my-greeting name="小林"> 就会显示 “Hello, 小林!”
3. Shadow DOM:样式的“结界”
假设你在全局写了 p { color: red; },会不会影响组件内的 <p>?不会! 因为 Shadow DOM 创建了一个“隔离空间”。
修改 greeting.js:
class MyGreeting extends HTMLElement {
constructor() {
super();
// 创建 Shadow DOM
const shadow = this.attachShadow({ mode: 'open' });
const name = this.getAttribute('name') || '陌生人';
shadow.innerHTML = `
<style>
p {
color: blue;
font-weight: bold;
/* 这个样式只在组件内部生效 */
}
</style>
<p>Hello, ${name}!</p>
`;
}
}
customElements.define('my-greeting', MyGreeting);
即使你在 <style> 里写 p { color: green; },组件内的文字依然是蓝色!这就是 Shadow DOM 的魔力。
4. Template:更优雅的模板写法
当结构复杂时,用字符串拼接很麻烦。我们可以用 <template> 标签预定义结构。
在 index.html 的 <head> 中加入:
<template id="greeting-template">
<style>
p { color: purple; }
</style>
<p>Hello, <span id="name-slot"></span>!</p>
</template>
然后在 JS 中克隆它:
class MyGreeting extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
// 克隆模板
const template = document.getElementById('greeting-template');
const instance = template.content.cloneNode(true);
// 填充名字
const name = this.getAttribute('name') || '陌生人';
instance.querySelector('#name-slot').textContent = name;
shadow.appendChild(instance);
}
}
customElements.define('my-greeting', MyGreeting);
这样 HTML 和 JS 分离,代码更清晰!
四、实战:做一个“用户信息卡片”
我们来做一个稍微复杂的组件:显示用户头像、姓名和简介。
步骤 1:HTML 结构
<!-- index.html -->
<user-card
avatar="https://via.placeholder.com/50"
name="小林"
bio="前端爱好者,爱写博客">
</user-card>
步骤 2:编写组件(user-card.js)
class UserCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
// 获取属性
const avatar = this.getAttribute('avatar') || '';
const name = this.getAttribute('name') || '匿名用户';
const bio = this.getAttribute('bio') || '暂无简介';
shadow.innerHTML = `
<style>
.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
max-width: 300px;
font-family: Arial, sans-serif;
}
.header {
display: flex;
align-items: center;
gap: 12px;
}
.avatar {
width: 50px;
height: 50px;
border-radius: 50%;
}
.name {
font-size: 18px;
font-weight: bold;
}
.bio {
margin-top: 12px;
color: #555;
}
</style>
<div class="card">
<div class="header">
<img class="avatar" src="${avatar}" />
<div class="name">${name}</div>
</div>
<div class="bio">${bio}</div>
</div>
`;
}
}
customElements.define('user-card', UserCard);
步骤 3:引入脚本
在 index.html 的 <body> 底部加上:
<script src="user-card.js"></script>
刷新页面,一个漂亮的用户卡片就出现了!
💡 小技巧:你可以把这个组件复制到任何 HTML 页面,甚至 React 项目里直接用!因为它是原生的。
五、新手常见问题解答
Q1:Web Components 和 React 组件有什么区别?
| 对比项 | Web Components | React 组件 |
|---|---|---|
| 依赖 | 无,浏览器原生支持 | 需要 React 库 |
| 学习成本 | 低(只需 JS + HTML) | 较高(JSX、状态管理等) |
| 生态 | 较小 | 非常丰富 |
| 适用场景 | 简单 UI 组件、跨框架复用 | 复杂交互应用 |
Q2:后端同学适合用 Web Components 吗?
非常适合! 如果你只是需要展示数据、做简单交互(比如表单、卡片、按钮),完全不用学 React。直接用 Web Components + 原生 JS 就能搞定,省时省力。
Q3:浏览器兼容性如何?
现代浏览器(Chrome 54+、Firefox 63+、Safari 10.1+、Edge 79+)都支持。如果需要兼容老版本 IE,可以用 polyfill。
Q4:怎么响应属性变化?
目前我们的组件只在初始化时读取属性。如果后续属性变了(比如通过 JS 修改),组件不会自动更新。解决方法是监听属性变化:
static get observedAttributes() {
return ['name', 'bio']; // 监听哪些属性
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
// 重新渲染
this.render();
}
}
完整实现略复杂,初学者可先忽略,知道有这个机制就行。
六、学习建议与下一步
Web Components 是一个“轻量级”的组件化方案,特别适合以下场景:
- 写通用 UI 库(比如按钮、弹窗)
- 后端快速搭建管理界面
- 在多个不同技术栈的项目中复用组件
下一步可以学:
- 生命周期方法:
connectedCallback、disconnectedCallback - 插槽(Slot):实现类似 Vue 的
<slot>功能 - 与框架集成:如何在 React 中使用 Web Components
- 工具链:用 Lit(轻量库)简化开发
我当初就是从 Web Components 入门,再过渡到 React 的。理解了“组件”的本质,学任何框架都会更快!
希望这篇教程能帮你打开原生组件化的大门。记住:技术没有高低,只有合适与否。 如果你的需求简单,Web Components 可能就是那把最趁手的“瑞士军刀”。
有问题欢迎留言讨论!我是小林,一个爱写代码也爱写教程的研究生,我们下期见!

评论 0