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

出色的守护者
2025-06-19 13:08
阅读 632

开篇:为什么我要聊聊这个话题?

开篇:为什么我要聊聊这个话题?

去年公司接了一个大型后台管理系统重构项目。我们的前端团队用的是 React,虽然整体结构还算清晰,但随着业务增长,组件间的耦合越来越高,维护起来也越来越吃力。

更头疼的是,我们还有一部分历史代码是基于 Vue 的旧项目需要集成进来。当时我们就想:有没有一种方式,能绕过框架差异,实现真正意义上的“组件复用”?

就在那个时候,我重新关注起了 Web Components。它早就有耳闻,但说实话一直觉得太冷门、不好用。不过在那次项目中,我们抱着试试看的心态引入了 Web Components 的概念,并且用上了一些现代封装技术(如 Lit),结果大大超出预期——不仅解决了跨框架通信的问题,还在性能和可维护性上带来了不少惊喜。

所以今天我就结合自己的真实项目经验,来聊一聊:

为什么 Web Components 正在成为组件化开发的新趋势?
在实际项目中,它是怎么帮我们解决问题的?


背景和问题描述:一个让人挠头的项目需求

背景和问题描述:一个让人挠头的项目需求

项目背景很简单:把老后台系统的核心功能模块抽离出来,以组件的形式提供给其他项目使用。其中涉及几个关键技术挑战:

  • 不同子系统使用不同框架(React + Vue + 部分原生 JS)
  • 组件样式和行为需要高度一致
  • 需要支持异步加载和懒加载
  • 希望组件能够做到真正的封装(样式不污染全局)

一开始我们考虑了几种方案:

  1. 用微前端 —— 模块间隔离彻底,但太重,且不利于细粒度交互。
  2. 封装为 npm 包供各框架调用 —— 样式容易冲突,组件状态管理复杂。
  3. 尝试 Web Components —— 听起来很理想,但不确定实际能不能行得通。

最终选择了第三个方向作为实验对象,主要出于两点考虑:

  • Web Components 是原生浏览器支持的标准,理论上可以在任何框架中使用
  • Shadow DOM 可以天然隔离样式作用域,避免全局污染

于是我们开始了对 Web Components 的深入实践……


解决方案与实现思路

技术选型

我们决定采用 Lit 这个库来进行组件封装。Lit 是 Google 团队维护的一个轻量级 Web Components 库,基于原生 JavaScript,提供了响应式能力、模板渲染等现代开发体验,同时又不绑定任何主流框架。

我们还搭配了如下工具链:

  • rollup:用于打包并生成兼容性更强的输出(ESM + UMD)
  • TypeScript:提升开发体验和类型安全性
  • Storybook:本地调试和文档化组件展示
  • postcss + autoprefixer:处理 CSS 兼容性

整个构建流程大致如下:

src/Components/
    └── MyComponent.ts → 编写组件逻辑
rollup.config.js        → 构建配置
storybook/              → 展示故事书
dist/                   → 打包后的组件库输出目录

举个例子:一个简单的按钮组件

比如我们要封装一个基础按钮组件,支持传入 label、disabled 状态、点击事件等属性。

import { LitElement, html, css } from 'lit';
import { customElement, property } from 'lit/decorators.js';

@customElement('my-button')
export class MyButton extends LitElement {
  @property({ type: String }) label = '';
  @property({ type: Boolean, reflect: true }) disabled = false;

  static styles = css`
    :host {
      display: inline-block;
    }
    button {
      padding: 8px 16px;
      background-color: #007bff;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    button:disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }
  `;

  render() {
    return html`
      <button ?disabled=${this.disabled} @click=${this.handleClick}>
        ${this.label}
      </button>
    `;
  }

  private handleClick() {
    this.dispatchEvent(new CustomEvent('click'));
  }
}

这个组件会被打包成独立的 .js 文件,然后通过 <script> 引入即可使用,比如:

<script src="https://yourcdn.com/my-button.js"></script>
<my-button label="提交" @click="doSomething"></my-button>

它可以在 Vue 中使用,也可以在 React 中使用,甚至可以放在纯 HTML 页面里跑!


实践中的关键点与坑点分享

前端开发工具界面-1

✅ 成功的关键:Shadow DOM 和样式隔离

这可能是 Web Components 最核心也最容易被忽视的优点之一。

我们之前经常遇到某个组件的样式不小心覆盖了全局 CSS,导致页面布局错乱。而使用 Shadow DOM 之后,每个组件内部都像是一个独立的小世界,CSS 完全互不影响。

比如上面的例子中,我们在组件内部写的 button { ... } 并不会影响外面的其他按钮样式。

❗️踩过的坑 1:如何调试 Shadow DOM 里的内容

刚开始时我发现 Chrome DevTools 默认是不会展开 Shadow DOM 的内容的。如果你不手动开启相关设置,会以为元素没正确渲染。

解决方法:

  1. 打开 DevTools 设置(右下角 ⚙️)
  2. 找到 “Preferences” -> “Elements”
  3. 勾选 “Show user agent shadow DOM”

这样你就可以看到 Shadow DOM 内部的内容了,方便调试样式和元素。

❗️踩过的坑 2:跨框架事件传递

虽然 Web Components 支持通过 CustomEvent 来传递数据,但在一些框架内(比如 React)并不会自动识别这种自定义事件。

比如下面这段 React 使用方式就不会触发回调:

<MyButton onClick={() => alert('clicked!')} />

因为 React 对非标准事件名不太友好,这时候需要用原生方式绑定监听:

useEffect(() => {
  const el = document.querySelector('my-button');
  const handler = () => alert('clicked!');
  el?.addEventListener('click', handler);
  return () => el?.removeEventListener('click', handler);
}, []);

或者,在 Web Components 里将事件改为小写命名:

this.dispatchEvent(new CustomEvent('click')); // 小写,更通用

并在 React 使用时改用驼峰转小写的格式:

<MyButton click={handleClick} />

❗️踩过的坑 3:样式暴露给外部定制

有时候我们也希望允许使用者自定义组件样式,这就需要借助 ::part()::slotted() API。

比如我们给按钮加一个自定义类名插槽:

<my-button><span slot="icon">⚙️</span> 设置</my-button>

然后组件内部:

<slot name="icon"></slot>

这样用户就可以自定义图标位置,配合 ::slotted([slot='icon']) 来为其添加样式。

这种方式让组件既保持了封装性,又能对外提供一定程度的可定制性。


结果与收益分析

经过三个月的实践打磨,我们完成了第一批 Web Components 的封装工作,包括表单输入、表格、弹窗、按钮等常用组件。

最终效果非常不错:

  • 多框架共存不再难:Vue、React、jQuery 项目都能统一调用同一套 UI 组件
  • 样式无冲突:得益于 Shadow DOM,各个组件之间互不影响,省去了很多样式调试时间
  • 构建体积变小:相比引入一个完整 UI 库,单独打包的 Web Components 更轻量
  • 开发体验升级:结合 Storybook 做组件文档和演示,提高了协作效率

更重要的是,我们这套组件逐渐成为了公司内部 UI 规范的一部分,被多个产品线引用。


我的一些建议和注意事项

1. 别再执着于框架之争了

现在很多人还在争论 Vue 好还是 React 好。但在 Web Components 面前,这些都可以共存。如果目标是打造一套稳定、可复用的 UI 组件系统,Web Components 绝对是一个值得投资的方向。

2. 注意兼容性问题

虽然现代浏览器对 Web Components 支持良好,但如果你还需要兼容 IE11 或低端移动端设备,就需要引入 polyfill。例如:

npm install @webcomponents/webcomponentsjs

然后在入口文件头部加上:

import '@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js';
import '@webcomponents/webcomponentsjs/webcomponents-bundle.js';

当然,现在大部分企业级应用已经不用支持那么老旧的浏览器了。

3. 工具链要统一

如果你打算把 Web Components 推广到整个团队,建议从一开始就统一构建流程。我们是使用 Rollup + TypeScript + PostCSS 的组合,配合统一的 Git Hook 和 CI 流程,保证组件库的质量一致性。

4. 调试和测试不能少

对于 Web Components,我们采用了 Puppeteer 编写 E2E 测试,同时用 Jest 做单元测试。推荐大家也做类似的事情,尤其是针对事件触发、属性绑定等细节场景。


总结

回过头来看这次尝试,其实 Web Components 并不是为了替代框架而存在的,而是为了解决框架无法很好处理的 组件复用样式隔离 的问题。

它不像 Vue/React 那样给你完整的生态,但它轻量、标准、通用,尤其适合打造 UI 组件库、微组件、跨平台组件这类场景。

在未来,我非常看好 Web Components 会和现代框架进一步融合,成为组件化开发的重要一环。如果你也有类似跨项目复用、统一 UI 风格的需求,不妨从一个小部件开始尝试一下,说不定会有意想不到的收获。

毕竟,真正优秀的开发者,永远不应该被技术栈所限制,而应该用合适的技术去解决实际问题


最后送一句我在项目中常常提醒自己同事的话:

“技术是用来解决问题的,而不是用来争输赢的。”

评论 0

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