Web Components:在复杂项目中找到组件化的原生解法
开篇背景

作为一名前端工程师,我在过去几年里参与了多个大型企业级 Web 项目的开发。随着 Vue、React 等现代框架的广泛普及,我们团队在开发中一直依赖这些框架进行组件化开发。但当一个新项目来了——目标是打造一套可在多平台通用、不依赖特定框架、轻量且易于集成的 UI 组件库时,我第一次认真思考起“如果不用 React 或 Vue,还能怎么做组件化开发?”。
答案指向了 Web Components。
遣使而来的挑战

这次我们要为公司搭建一套面向外部客户的 UI 设计系统,并提供一组可部署于任意技术栈下的基础组件(如按钮、输入框、表单控件等),同时要求:
- 支持主流浏览器兼容性
- 不依赖任何前端框架
- 可被第三方轻松引入使用
- 有统一的样式管理与交互逻辑
- 性能要尽可能优
当时我们在调研过程中发现:
- 使用传统 jQuery 插件或纯 HTML/CSS 拼接的方式难以维护;
- 若坚持用框架实现,就必须让用户也安装对应框架(React、Vue)才能使用我们的组件,这明显不可行;
- 基于 Shadow DOM 封装 CSS 和行为似乎是个可行方向,于是把目光投向了 Web Components。
技术选型与方案制定
Web Components 并不是一个单一的技术,而是包括:
- Custom Elements(自定义元素)
- Shadow DOM(影子 DOM)
- HTML Templates(HTML 模板)
- ES Modules(模块加载)
我们决定采用原生 Web Components API 结合简单的类库封装来构建组件系统。
🛠️ 小工具辅助选择:虽然原生 APIs 已经很强大,但我们最终引入了 Lit 这个轻量级类库,它基于 Web Components 标准构建,在性能、易读性和开发者体验上都有不错的表现。
实战演练:以“带提示文本的输入框”为例
场景说明
我们需要一个支持提示文字(placeholder)、错误状态(error state)、禁用状态(disabled)等特性的 <custom-input> 组件。它应该能够独立运行,无需依赖任何框架。
文件结构设计
components/
├── custom-input/
│ ├── index.js ← 自定义元素注册
│ ├── input.css ← 组件样式
│ └── input.html ← HTML Template(可选)
主要代码实现
// components/custom-input/index.js
import { html, css, LitElement } from 'lit';
import styles from './input.css' assert { type: 'css' };
export class CustomInput extends LitElement {
static properties = {
placeholder: { type: String },
error: { type: Boolean },
disabled: { type: Boolean }
};
static styles = [styles];
constructor() {
super();
this.placeholder = '';
this.error = false;
this.disabled = false;
}
render() {
return html`
<div class="custom-input-wrapper">
<input
type="text"
.placeholder=${this.placeholder}
?disabled=${this.disabled}
class="${this.error ? 'has-error' : ''}"
/>
${this.error ? html`<span class="error-message">请输入正确内容</span>` : ''}
</div>
`;
}
}
customElements.define('custom-input', CustomInput);
对应的 input.css:
.custom-input-wrapper {
display: flex;
flex-direction: column;
}
input {
padding: 8px 10px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
outline: none;
}
input.has-error {
border-color: #e74c3c;
}
.error-message {
color: #e74c3c;
font-size: 12px;
margin-top: 4px;
}
然后直接在页面中调用即可:
<custom-input placeholder="输入你的名字" error disabled></custom-input>
这就是一个完整的无框架组件。
踩过的坑和应对经验
✅ 兼容性处理
尽管 Web Components 在 Chrome、Firefox 中已良好支持,但在部分老版本浏览器(比如 IE11)上完全无法运行。我们最终采取了以下策略:
- 主推现代浏览器支持,放弃对 IE 的兼容
- 对旧项目迁移场景,保留了 Vue 版本作为过渡方案
- 引入 polyfill 仅针对少数遗留客户使用场景(通过动态加载判断浏览器类型)
❌ 动态样式传参问题
初期想通过属性传入不同主题色或者尺寸参数,却发现 CSS 变量在 shadow dom 内无法直接获取宿主元素的变量值。最后我们改用属性绑定 + JS 动态操作样式对象的方式解决。
this.style.setProperty('--input-bg', '#eee');
并在模板内 CSS 使用该变量:
:host {
--input-bg: white;
}
input {
background-color: var(--input-bg);
}
这样实现了从外层传递主题配置的能力。
⚠️ 开发调试小技巧
- 使用
Chrome DevTools → Elements → 展开 #shadow-root查看 shadow dom 内部结构。 - 使用 Lit 提供的测试辅助包 lit/jest-dom 来编写单元测试。
- 利用 VSCode 插件 Lit support for Visual Studio Code 提高开发效率。
最终落地效果
我们最终交付了一套包含 15+ 个常用控件的组件库,它们:
- 完全脱离 React/Vue 独立运行
- 可嵌入到任意前端框架内部使用
- 包体大小控制在 60KB gzipped
- 提供清晰文档并附有 Storybook 示例库
- 支持无障碍访问(a11y)及响应式布局
上线后接入速度非常快,客户反馈也很正面。很多同事评价“终于有一个能像 HTML 原生标签一样用的 UI 库”。
心得总结与建议
如果你也在考虑是否使用 Web Components 技术:
✅ 建议尝试的场景
- 构建跨框架可复用的 UI 组件
- 微前端架构下需要隔离组件样式与行为
- 想做渐进式升级(从 jQuery 逐步过渡)
- 创建可公开发布的设计系统或开源组件库
🚫 暂时不推荐的情况
- 项目周期短、急需快速交付,因生态和学习曲线仍存在门槛
- 团队缺乏对原生 JavaScript 掌握能力
- 要求高度动态交互(如动画/游戏/数据可视化)
🚀 我的一些建议
- 如果选择原生 API 编写,请熟悉 Shadow DOM 的生命周期方法(connectedCallback/disconnectedCallback);
- 推荐结合 Lit 使用,可以省去大量样板代码;
- 对样式封装要小心,避免污染全局环境;
- 善用自定义事件(dispatchEvent)来与父组件通信;
- 组件间通讯可以用 context api、pub-sub 方式替代 Redux;
- 一定要做单元测试,推荐使用 Playwright + Jest。
尾声:Web Components 正当时
虽然 React/Vue 还是现在开发中的主力,但我相信,组件化的本质其实是与框架无关的,而 Web Components 提供了一个真正标准化、原生支持的路径。
在这个追求极致灵活、轻量化、跨平台的时代,也许未来的组件标准就藏在每一个 <template> 和 customElements.define() 的组合之中。
如果你也有类似的经验,欢迎留言交流~

评论 0