Web Components:原生组件化开发新趋势

半栈青年
2025-06-14 16:17
阅读 560

开篇

开篇

去年年中,我参与了一个比较大的项目,背景是一个大型企业级系统,里面包含大量的表单、数据展示模块和UI交互控件。整个系统采用传统的Vue + Vuex架构,但在多个业务线同时迭代下,出现了不少“重复造轮子”、“样式冲突”、“跨团队协作困难”的问题。那时候我们就一直在想,有没有一种更“通用”又能保持性能的方式,来构建这些共用的 UI 组件?

于是我们开始探索一些非框架的解决方案。最让我感兴趣的是 Web Components —— 一个基于浏览器原生能力的技术方案。虽然这个概念并不新鲜,但随着现代浏览器支持越来越好,它真的可以成为组件化开发的一条新出路。

这篇文章就想从我实际工作中的经历出发,聊聊我们在 Web Components 方面的实践过程,踩过的坑,以及最终带来的收益和思考。


我们遇到了什么问题?

我们遇到了什么问题?

当时我们的前端工程面临几个很现实的问题:

  1. 多框架共存带来维护成本上升
    不同业务线使用了 Vue2、Vue3、React 甚至 Angular,导致很多基础组件都要为每个框架单独实现一套。不仅人力浪费大,样式一致性也难以保障。

  2. 样式污染严重
    每个小组自己定义 CSS 样式,经常出现 class 冲突、全局污染。即使用了 CSS Modules,也很难做到完全隔离。

  3. 组件可移植性差
    一旦某个组件需要在其他框架或项目中使用,就必须进行大量适配改造,几乎相当于重新写一遍。

  4. 沟通成本高
    各组之间对组件理解不统一,文档更新慢,大家各自改自己的版本,最后变成一团乱麻。

这些问题逼着我们不得不重新审视目前的架构方式。我们需要一种框架无关、封装良好、易维护、可复用的组件方案。Web Components 正好切中要害。


我们的解决方案:选择 Web Components

我们决定尝试用 Web Components 来构建一些通用的基础组件。比如:按钮、输入框、表格、弹窗、树形结构等。目标是把这些组件抽象出来作为“原子”,供所有项目直接调用,不需要依赖特定框架。

Web Components 的核心技术包括:

  • Custom Elements(自定义元素)
  • Shadow DOM(影子节点)
  • HTML Templates(模板)
  • ES Modules(现代 JavaScript 模块)

它们组合在一起,能让我们创建出真正意义上的“封装组件”,具有独立作用域和行为,并可以在任意 HTML 页面中通过原生标签调用。


实践过程:一步步搭建一个 Web Component

项目初始化

我们选用了 Vite 作为构建工具,因为它天然支持 ES Modules,并且对 TypeScript 集成友好。整个工程结构非常简单:

src/
├── components/
│   └── my-button/
│       ├── index.js
│       └── template.html
├── index.html
└── main.js

每个组件都作为一个 Custom Element 注册到 window 上。以下是 my-button/index.js 的简要实现:

import template from './template.html?url';
import { fetchTemplate } from '../utils';

class MyButton extends HTMLElement {
  constructor() {
    super();

    this.shadow = this.attachShadow({ mode: 'open' });

    // 加载 html 模板内容
    fetchTemplate(template).then((html) => {
      const temp = document.createElement('template');
      temp.innerHTML = html;
      const clone = temp.content.cloneNode(true);
      this.shadow.appendChild(clone);

      // 获取按钮并绑定点击事件
      const button = this.shadow.querySelector('button');
      button.addEventListener('click', () => {
        const event = new CustomEvent('click', {
          bubbles: true,
          composed: true,
        });
        this.dispatchEvent(event);
      });
    });
  }
}

customElements.define('my-button', MyButton);

上面的代码做了几件事:

  • 使用 Shadow DOM 封装内部结构和样式
  • 引入外部 HTML 模板并通过 fetchTemplate 动态加载
  • 在 shadow root 中插入 HTML 结构
  • 给按钮添加事件监听器,并触发自定义事件以便外部监听

这种方式的好处是结构、样式和逻辑都隔离得很好,不会影响页面其他部分。


遇到的坑与解决办法

坑1:无法直接使用框架的功能(如 Vue/React)

这是 Web Components 最让人纠结的地方。由于它是原生机制,不能像框架那样使用响应式状态管理或者 JSX。但我们并没有追求“全能”,而是明确其定位:只用来做基础组件封装

我们给各个业务团队提供了一份简单的使用指南,说明如何引入组件、监听事件、传参等。

坑2:样式穿透

虽然 Shadow DOM 默认隔离了样式,但有时候我们希望父级样式能够对子组件生效,比如字体颜色、字号等。这时候可以通过 inherit 或使用 CSS 变量来传递样式:

:host {
  font-size: var(--my-font-size, 14px);
}

然后在宿主页面设置:

<style>
  body {
    --my-font-size: 16px;
  }
</style>

这样就能灵活控制公共组件的外观而不过度耦合。

坑3:兼容性和 polyfill 支持

虽然现代浏览器基本都支持 Web Components,但对于 IE11 这种老家伙还是得借助 polyfill。我们使用的是官方推荐的 webcomponentsjs,通过动态判断用户 UA,按需加载 polyfill 脚本。

当然,这种策略不是万能的,polyfill 会增加包体积,某些高级特性也无法完美模拟。所以我们建议如果项目需要兼容老旧环境,需要评估是否值得投入。


成果与反馈

经过大约两个月的努力,我们将 20 多个常用组件进行了封装,并集成到了不同项目中。效果还是很明显的:

移动端适配方案-2

项目类型 组件复用率提升 跨框架集成耗时 样式冲突频率
Vue 项目 90%+ 几乎无 显著下降
React 项目 85%+ 略微适配 props 即可 基本解决
独立 HTML 页面 完全开箱即用 无需适配 无冲突

团队之间的协作效率明显提高,文档编写压力也小了很多,因为接口统一了,大家只需要看一个示例即可。

而且最重要的是,这些组件不再依赖某个框架版本,未来即使技术栈升级,这些 Web Components 依然可用。


一些建议与注意事项

响应式布局概念图-1

如果你也在考虑使用 Web Components,以下几点是我从实践中总结出来的经验:

  1. 别期望一步到位解决所有问题
    Web Components 更适合做底层组件库,不适合复杂应用层逻辑。如果你的组件本身就有很多状态、动画、交互,可能更适合在框架里处理。

  2. 结合 ES Module 和现代打包工具
    使用 Rollup 或 Webpack 构建 Web Components 库可以优化输出大小和兼容性。对于小型项目,Vite 是个很好的选择。

  3. 注意事件传递机制
    自定义事件要用 CustomEvent 并设置 bubblescomposed 为 true,确保能正确冒泡到上层。

  4. 合理使用 CSS 变量和继承样式
    保证组件外观可定制,但又不至于破坏封装性。

  5. 测试一定要跟上
    Web Components 很容易被误用,最好配合单元测试(Jest / Testing Library)和文档自动化生成(如 Storybook)来规范使用方式。

  6. 关注浏览器兼容性报告
    Can I Use - Custom Elements


写在结尾

Web Components 并不是万能药,但它确实给我们带来了全新的思路和更轻量化的组件封装方式。特别是在如今前端技术频繁更迭的大环境下,它提供的那种“一次开发,到处可用”的感觉,真香。

如果你也在做跨团队协作、跨框架迁移、组件共享平台之类的事情,不妨试试 Web Components。它也许不是你唯一的答案,但一定是值得认真考虑的一个选项。

在这个项目之后,我也深刻意识到,真正的优秀架构,不是建立在某一个框架之上,而是建立在浏览器本身的能力之上。用标准的东西解决问题,才是最稳当的做法。


希望这篇来自实战的文章能给你一些启发。如果你已经尝试过 Web Components 或者有相关疑问,欢迎留言交流。我们一起打造更有生命力的前端生态 👇

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝