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

徐娟_数据
2025-06-29 19:28
阅读 337

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

去年我们在一个大型项目中尝试了 Web Components,结果让我非常惊喜。这不是一篇理论文章,而是我们团队在真实开发过程中踩过的坑、做过的权衡,以及最终收获的收益。这篇文章里,我会用最真实的视角跟你聊聊为什么我们会选择 Web Components,它在实际开发中解决了哪些问题,还有那些让人抓头的技术细节。

如果你正在考虑要不要引入 Web Components 或者想了解它在真实项目中的表现,那这篇应该能帮到你。我们是从 Vue 和 React 混合技术栈迁移到 Web Components 的,整个过程有曲折也有成长,希望这些经验能帮你少走点弯路。


背景:为什么要尝试 Web Components?

背景:为什么要尝试 Web Components?

我们当时维护着一个内部平台,这个平台需要支撑多个产品线,而且不同产品线的技术栈不统一。前端这边同时存在 Vue 2、Vue 3 和 React 16,每次要新增一个通用组件(比如按钮或弹窗),都要分别写三遍,并且维护起来相当痛苦。

更头疼的是,这些组件的样式和行为也很难保持一致。虽然我们都尽力统一设计系统,但因为技术实现不同,最终出来的效果总有细微差异。再加上一些老项目根本没办法升级框架版本,很多功能更新只能通过手动复制粘贴来实现。

这时候我们就想:有没有一种可以跨框架复用的方式?


我们遇到的挑战

我们遇到的挑战

起初我们是使用 Shadow DOM + Custom Elements API 自己封装了一些基础组件。看起来没问题,但随着组件复杂度上升,我们发现几个大问题:

  1. 事件传递和通信麻烦:尤其是组件嵌套时,父子元素之间的数据交互很难处理。
  2. 样式隔离导致主题切换困难:Web Components 的 Shadow DOM 默认样式隔离太强,当我们想动态修改主题颜色时,变得很麻烦。
  3. 缺乏开发工具支持:Chrome DevTools 对 Web Components 的调试体验差,调试属性、生命周期都不方便。
  4. 兼容性问题:虽然主流浏览器基本都支持了,但旧版 IE 和一些低版本移动浏览器还是不支持。

于是我们不得不深入研究 Web Components 相关生态,包括如何更好地组织代码结构、优化渲染性能以及解决样式问题。


我们的解决方案:选型与架构设计

经过一番调研和对比,我们决定采用以下方案组合:

  • 基于 Lit 开发 Web Components,它是一个轻量级的库,基于原生模板语法提供响应式能力和便捷开发体验;
  • 使用 Vite + TypeScript + Rollup 构建组件库;
  • 所有公共组件统一打包成 NPM 包供各业务线接入;
  • 通过 CSS Custom Properties 解决主题样式的灵活性问题;
  • 引入 Playwright 编写 UI 组件测试;
  • 对于需要兼容旧浏览器的场景,使用 Babel 和 polyfill 回退。

用户交互流程图-1

这样的架构让我们实现了几个目标:

  1. 一套组件,多端可用:可以在 Vue、React、甚至 jQuery 项目中直接使用;
  2. 统一设计语言:由于共享了同一个组件库,UI 层的一致性大大提升;
  3. 可扩展性强:新组件很容易添加,旧项目也能逐步迁移;
  4. 开发效率提升:借助 Lit 的简洁 API,开发和维护成本降低了不少。

代码实践:一个带状态的按钮组件

以我们最常见的按钮组件为例,来看看怎么用 Lit 来实现一个具备点击计数功能的按钮。

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

@customElement('counter-button')
export class CounterButton extends LitElement {
  @property({ type: Number })
  count = 0;

  static styles = css`
    button {
      background-color: var(--button-bg, #0366d6);
      color: white;
      padding: 10px 20px;
      border-radius: 4px;
      border: none;
      cursor: pointer;
    }
  `;

  render() {
    return html`
      <button @click=${this.handleClick}>
        点击次数:${this.count}
      </button>
    `;
  }

  private handleClick() {
    this.count++;
    this.dispatchEvent(new CustomEvent('count-changed', {
      detail: { count: this.count },
      bubbles: true,
      composed: true,
    }));
  }
}

使用方式

在 HTML 中直接使用:

<counter-button count="5"></counter-button>

在 Vue 或 React 中使用:

只需要确保你的构建工具正确打包,并全局注入自定义元素即可。不需要任何适配器。对于 React 项目,还需要添加一行配置:

// 忽略 React 对未知标签的警告
/* global JSX */
declare module 'react' {
  namespace JSX {
    interface IntrinsicElements {
      'counter-button': any;
    }
  }
}

动态主题色支持

我们可以轻松地通过 CSS 变量来控制按钮的颜色,在全局或者页面级别设置变量即可生效:

:root {
  --button-bg: #e91e63; /* 主题色 */
}

这样就可以在整个应用中动态更换主题而无需额外 JS 逻辑。


遇到的坑及填坑过程

Web Components 不是银弹,我们在实际使用中踩过不少坑,这里挑几个关键点分享一下。

1. Shadow DOM 隔离带来的样式限制

最初我们把所有组件都包裹在 Shadow DOM 里,后来发现有些高级功能比如 Portal(用于弹出层)就失效了——因为 Shadow DOM 是封闭的,不能访问外部的 DOM 节点。

解决方法

  • 对于需要挂载到 body 上的组件(如 Modal),我们干脆不使用 Shadow DOM,改用 <div> 并挂在 body 下面;
  • 同时引入命名空间类名,避免样式污染;
  • 利用 Scoped 样式配合 CSS-in-JS 工具实现更灵活的布局控制。

2. 生命周期不直观,容易造成内存泄漏

Web Components 的生命周期函数不像现代框架那样明确。例如,connectedCallback 相当于 mount,disconnectedCallback 相当于 unmount,但我们曾经在一个定时器组件里忘了清除 timer,导致页面卡顿。

解决建议

  • 为每个组件单独管理副作用;
  • 引入类似 useEffect 的封装;
  • 手动清理资源时一定要记得在 disconnectedCallback 中释放。

3. 浏览器兼容性问题

虽然大部分现代浏览器都支持 Web Components v1 规范,但在客户现场部署时遇到某些低端 Android 设备,居然连 ES Modules 都无法运行!

解决办法

  • 使用 Babel 将代码转译成 ES5;

  • 引入 Polyfill,比如:

    npm install @webcomponents/webcomponentsjs
    

    并在入口处加载:

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

效果总结:迁移后的变化

用户交互流程图-2

从原来的“各自为政”到现在的统一组件库,整体上我们的研发流程发生了很大的改善:

方面 迁移前 迁移后
组件一致性 多套实现,风格不一 单库维护,风格统一
维护成本 高(重复开发) 显著降低
技术栈绑定 强依赖框架 完全解耦
性能表现 无明显瓶颈 更轻量,更快启动
新人上手难度 学习曲线陡峭 组件文档清晰,易上手

特别是在我们后续重构一个中台项目时,几乎一天之内就完成了十几个核心组件的替换,而且没有引起用户侧的反馈。

还有一个意外的收获是我们终于告别了“框架之争”——因为无论谁用什么技术栈,只要调用同一个组件库即可,大家不再争执该不该升级 React 版本或者是否要迁移至 Vue 3。


给读者的建议和注意事项

如果你也在考虑采用 Web Components,我有几个来自实战的小建议:

✅ 建议这么做

  1. 从小范围开始试验:先从一些简单的非核心组件入手,比如按钮、输入框等。
  2. 结合 Lit 或 stencil 使用:不要裸写原生 Custom Element,开发体验真的差太多了。
  3. 使用 TypeScript:类型安全在组件间通信时特别重要。
  4. 组件尽可能 Stateless:尽量让组件专注于 UI 表现,减少对状态管理的依赖。
  5. 利用 CSS Custom Properties 实现主题化:这对视觉层面的复用非常重要。

⚠️ 注意事项

  1. 慎重使用 Shadow DOM:不是所有场景都需要它,特别是涉及 DOM 操作的组件。
  2. 提前规划好通信机制:组件之间传值最好通过标准事件传递。
  3. 兼容性要兜底:尤其在企业级应用场景中,别忽略了旧设备的支持。
  4. 注意 Tree-shaking 配置:如果只用了部分组件,但整包都打进去就有点浪费了。
  5. 配套文档和示例很重要:不然别的项目组可能不敢轻易引用。

未来展望

Web Components 正处于一个快速发展的阶段。越来越多的框架也开始集成其能力,比如 Vue 3 支持将 SFC 编译为 Web Component,React 也有相关实验性提案。

我相信在未来几年内,基于 Web Components 的“真正意义上的”跨框架组件生态将会更加成熟。对于我们这种拥有大量遗留系统的公司来说,它提供了一种“渐进迁移”的可行路径。

现在回过头来看,当初决定尝试 Web Components 算是我们团队在技术决策上的一个重要转折点。它不仅解决了组件复用的问题,更重要的是推动了前端工程化的标准化进程。


结语:Web Components 值得你尝试

Web Components 不是炫技,也不是噱头,它是解决组件跨项目复用、打破技术栈限制的一种务实之道。也许它的学习曲线不如 React/Vue 平滑,但它给我们的回报远超预期。

如果你还在纠结用哪种方式构建组件,不妨尝试 Web Components。也许刚开始会觉得“这玩意儿好像没什么特别”,但一旦在多个项目里打通了,你会发现它其实比你想的更有力量。

当然,这一切的前提是你要愿意动手试试看。


如需获取完整项目示例,请查看我们的 GitHub 公共仓库 https://github.com/yourname/components注:请替换为真实地址

📌 关注公众号「前端早茶」回复 “web-components” 获取实战源码。

评论 0

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