Web Components:一次组件化的“返璞归真”之路

自由鹰
2025-06-29 18:08
阅读 655

开篇:为什么我要写这篇文章?

开篇:为什么我要写这篇文章?

作为前端工程师,在过去的五年中,我有幸参与过不少大大小小的项目。从最初的jQuery时代一路走来,经历过Angular、React和Vue的更迭与成长。但直到最近半年,我才真正感受到一种前所未有的平静——那是在我们决定使用 Web Components 构建一个跨框架共享 UI 组件库时。

这并非一时兴起的选择,而是源于多个项目的积累教训和反复试错后的理性判断。我想把这些经验整理出来,分享给大家,尤其是那些在组件化开发之路上遇到瓶颈的同学。希望这篇来自真实踩坑经历的技术文章,能带给你一些启发或避坑经验。


问题描述:组件共享难题

问题描述:组件共享难题

故事要从去年的一个项目说起。当时我们公司正在做一套企业级产品系统,分为几个子产品线,分别由不同的团队负责。这些产品虽然功能不同,但用户界面风格高度统一,甚至有大量交互一致的组件(比如按钮、弹窗、表格等)。

最开始,我们尝试用 Vue 的 NPM 包封装基础组件,每个项目安装依赖后直接使用。结果理想很丰满,现实却很骨感:

  • 版本混乱:不同项目依赖的组件版本不一致,升级困难;
  • 耦合性高:组件内部依赖 Vue 的 API,导致非 Vue 项目无法使用;
  • 打包冲突:第三方库引入多个 Vue 实例,页面出现不可预知的错误;
  • 协作障碍:不同技术栈之间难以复用组件,沟通成本极高。

这个问题困扰了我们一段时间,也促使我们重新思考一个问题:

我们真的需要通过框架绑定来构建组件吗?


解决方案:Web Components 来救场

解决方案:Web Components 来救场

经过一番调研,我们最终将目光投向了原生支持的 Web Components 技术。它的核心理念很简单也很强大:

用浏览器原生的能力,定义可复用、独立且跨框架的 UI 组件。

我们决定采用 Web Components 来重构整个组件库,实现真正的“一份代码,多端复用”。目标非常明确:

  1. 构建一个不依赖任何框架的组件库;
  2. 所有子产品线可以直接使用;
  3. 支持未来接入更多技术栈(如 React、Svelte)。

听起来是不是有点像“梦想回归现实”?不过别急,这条路上的坑远比你想象得多。


实践过程:Web Components 真实落地之旅

实践过程:Web Components 真实落地之旅

我们的第一步是选型。

技术选型与工具链搭建

我们没有选择从头造轮子,而是借助了一个轻量级的帮助库 LitElement(后来改为 Lit),它大大简化了 Web Components 的开发流程。

主要优势包括:

  • 轻量级:几乎没有额外负担
  • 模板引擎好用(html 模板标签)
  • 自动更新机制智能
  • 与原生标准兼容性极佳

工具链方面我们使用:

  • Vite + TypeScript 构建开发环境
  • Rollup 打包生产模块
  • Jest + Playwright 做单元测试和 E2E 测试
  • Storybook 做组件演示文档

一切准备就绪后,我们开始了第一个组件—— <custom-button> 的开发。

第一个组件:<custom-button>

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

@customElement('custom-button')
export class CustomButton extends LitElement {
  @property({ type: String }) label = ''
  @property({ type: Boolean }) disabled = false

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

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

没错,这就是一个典型的基于 Lit 的 Web Component 定义方式。它完全封装了结构、样式和行为,而且无需任何框架支持。


踩坑记录:真实踩过的那些坑

说了半天美好的设想,接下来才是本文的重点部分:实际开发中踩到的真实大坑和解决方法。这部分我将详细分享几个关键问题和我的应对思路。

坑1:浏览器兼容性不如预期

你以为 Web Components 是现代浏览器的标准就能无脑上?Too young too simple。

在项目初期我们只测试了 Chrome 和 Edge,上线前发现某些老客户的 IE11 还没下线,根本无法渲染组件!

解决办法:

  • 使用官方 Polyfill:@webcomponents/webcomponentsjs
  • 针对旧浏览器自动加载:
<script src="path/to/webcomponents-bundle.js" defer></script>
  • 编译配置加入 Babel + ES5 转换:
// vite.config.ts 配置示例
optimizeDeps: {
  esbuildOptions: {
    target: 'es2015'
  }
}

坑2:样式隔离的边界模糊

Web Components 默认的 Shadow DOM 隔离了内部样式,看起来美好得像是解决了所有问题。但在实战中发现,有时候需要全局样式控制组件主题色、字体等通用设置。

我的做法是:

  • 使用 CSS 变量作为接口暴露给外部:
static styles = css`
  :host {
    --btn-bg: #4caf50;
    --btn-color: white;
  }

  button {
    background-color: var(--btn-bg);
    color: var(--btn-color);
  }
`

这样,上层应用可以通过修改变量来定制组件样式,同时又不会破坏内部样式结构。

坑3:事件传递不透明,调试难

Web Components 的事件默认都是原生 DOM 事件,但很多时候我们需要自定义事件(比如点击按钮时传参数)。

举个例子,我们希望在按钮点击时触发一个 custom-click 事件并传递数据:

const event = new CustomEvent('custom-click', {
  detail: { source: 'button', value: this.label },
  bubbles: true,
  composed: true
})
this.dispatchEvent(event)

但奇怪的是,我们在父级框架里监听这个事件的时候有时候收不到。

最终排查原因:

  1. 是否设置了 bubbles: true
  2. 是否设置了 composed: true?这是跨 Shadow DOM 传播的关键。
  3. 框架是否拦截/重写了原生事件?(Vue 没问题,有些框架需要手动处理)

坑4:性能优化不能掉以轻心

Web Components 本身不会自动帮你优化性能。尤其是频繁创建和销毁大量组件实例时,容易引发内存泄漏或卡顿。

我们之前在列表页使用 <custom-list-item> 渲染数千行数据,一度出现卡顿现象。

后来的优化方案:

  • 列表组件采用虚拟滚动(Virtual Scrolling),减少节点数量
  • 通过 WeakMap 缓存复杂计算结果
  • 对高频函数加防抖节流(特别是 resize 或 scroll 相关逻辑)
  • 小组件尽量静态化,避免过多响应式更新

效果总结:收益显著

这套 Web Components 组件库上线后,我们收获了很多意料之外的好处:

✅ 组件彻底解耦,可在任意框架中自由使用

现在我们有一个 React 项目也在用这个组件库,只需要简单封装一个适配器即可:

function App() {
  return (
    <custom-button label="提交" onClick={(e) => console.log(e.detail)} />
  )
}

✅ 多项目版本一致性大大提高

所有项目都引用同一个包,更新只需发布 npm 版本,极大减少了人工同步成本。

✅ 开发效率提升,UI 标准更加统一

有了 Storybook 文档 + 设计稿还原度高的组件库,新成员几乎不需要花时间熟悉 UI 交互细节。


心得体会:组件化开发的本质

在这次 Web Components 探索之旅中,我最大的感悟是:

组件化的核心不是技术,而是设计思维。

很多开发者一上来就想找现成的组件库,“能用就行”,但忽略了最重要的事情:你的组件库是否具备扩展性、可维护性和业务理解能力?

Web Components 强大的地方就在于它把组件交还给了开发者——你可以按自己的规则组织 UI 结构,而不是被框架牵着鼻子走。


给读者的建议:避开 Web Components 的常见误区

最后,结合我个人的经验,给正准备尝试 Web Components 的同学们几点建议:

🧱 不要盲目追求“纯原生”

Web Components 是原生的,但不代表你必须从零开始开发。合理使用工具库(如 Lit、Stencil)可以大幅提升开发体验和效率。

🧭 提前做好组件规划

不要边开发边改接口。每个组件的属性、事件、生命周期应该一开始就有清晰的设计文档,避免后期大规模重构。

🛠️ 工具链不要轻视

TypeScript、Storybook、Rollup、测试框架一个都不能少。否则你会在后续维护中吃尽苦头。

🔄 主动拥抱变化

Web Components 目前发展迅速,新的提案(如 Declarative Shadow DOM)和优化不断涌现。保持关注社区动态,及时迭代你的方案。


总结:Web Components 是未来的基石

回望这几年的技术演变,从 jQuery 到 SPA 再到组件驱动开发,Web Components 正在成为连接过去与未来的桥梁。

它未必适合每一个项目,但它确实提供了一种标准化、去中心化的组件解决方案,尤其适用于大型多团队协作、长期维护的项目。

如果你所在的团队也有类似的困境,不妨试试 Web Components。也许这条路并不平坦,但一旦走过,你会发现:

有些技术回归本质,反而走得更稳,也看得更远。


感谢你读到这里。如果文中有什么启发或者疑问,欢迎留言交流。也欢迎你将这篇文章分享给同样在组件化道路上挣扎的同学,愿你在 Web Components 的旅途中少走些弯路。

评论 0

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