Web Components:组件化开发的“原生”答案

设计稿别变了
2025-06-29 01:19
阅读 735

引言:组件化浪潮中的“新选择”

引言:组件化浪潮中的“新选择”

我第一次听到 Web Components 这个概念,是在一个项目的架构评审会上。那是一个需要支持多个子产品、多端共用组件的大型项目,我们当时已经尝试了 React 和 Vue 的组件库方案,但随着产品线的扩张和团队人员的流动,维护成本越来越高。

“有没有一种方式,可以让我们不再依赖框架?” 我抛出了这个问题。同事笑了笑:“有啊,就是 Web Components。”

说实话,那时候我对它只有一个模糊的印象——浏览器原生支持的自定义元素?听起来很酷,但似乎离实际落地还有点远。不过,在接下来的一年里,我的看法彻底改变了。


问题描述:跨框架协作的困境

问题描述:跨框架协作的困境

我们负责的是一款企业级 SaaS 平台,包含多个业务模块,分别由不同团队独立开发,使用不同的前端技术栈:

  • 财务模块:Vue.js
  • 客服系统:React
  • 管理后台:Angular(没错,你没听错)
  • 第三方集成:jQuery 混合写法的老项目

这本身不是什么大问题,直到我们要做统一的 UI 组件库时才暴露出痛点:

  1. 组件复用困难:每个团队都得重复实现类似功能的组件,比如按钮、弹窗、表格等。
  2. 样式冲突频繁:不同团队引入的 CSS 样式互相污染,尤其是第三方库和全局样式混在一起。
  3. 升级维护成本高:一次小改动可能要在多个仓库中同步更新,还容易出错。
  4. 性能难以统一优化:有些团队对渲染性能不敏感,导致整体页面加载慢。

这些问题最终演变成了一个严重的沟通瓶颈,甚至在几次上线过程中,UI 层因为样式或组件兼容性的问题而回滚发布。

这时候我才意识到:我们缺少一个真正“通用”的 UI 组件解决方案。


解决方案:回归原生的 Web Components

解决方案:回归原生的 Web Components

带着这样的思考,我又重新研究起了 Web Components。我发现它的几个核心特性非常契合我们的需求:

  • Custom Elements(自定义元素)
  • Shadow DOM(影子 DOM)
  • HTML Templates(模板)
  • ES Modules 支持

这些并不是新玩意,但结合当前浏览器的支持情况和 ES 模块生态,已经具备了实际应用的条件。

尝试阶段:从一个基础组件开始

我决定先做一个最小可行性验证,就从最基础的 <my-button> 开始,看看能不能跑通。

class MyButton extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    const button = document.createElement('button');
    button.textContent = this.getAttribute('label') || 'Click me';
    button.style.padding = '10px 20px';
    button.style.backgroundColor = '#007bff';
    button.style.color = '#fff';
    button.style.border = 'none';
    button.style.borderRadius = '4px';
    
    button.addEventListener('click', () => {
      this.dispatchEvent(new CustomEvent('my-click', { detail: 'custom event fired!' }));
    });

    shadow.appendChild(button);
  }
}

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

然后在 HTML 页面中直接使用:

<my-button label="提交"></my-button>
<script type="module" src="/components/my-button.js"></script>

这个例子虽然简单,却让我看到了无限可能——它不依赖任何框架,能在任意 HTML 页面中运行,并且自带封装性和样式隔离能力。

推广阶段:构建可扩展的组件体系

尝到甜头之后,我们决定围绕 Web Components 构建一套统一的 UI 组件库,并制定了以下目标:

  1. 所有组件必须通过 customElements.define() 注册
  2. 组件内部样式封装在 Shadow DOM 中
  3. 提供清晰的 API 文档,包含 props、events、slots
  4. 通过 npm 发布包,支持按需引入
  5. 兼容主流现代浏览器,IE11 不强制要求支持(基于项目实际情况)

技术选型:轻量封装 + 工程化支持

我们在实践中并没有完全从头造轮子,而是选择了两个工具来提升效率:

  • lit-html:Google 出品的轻量级模板引擎,配合 lit-element 使用体验很好
  • rollup:作为打包工具,能够将多个组件打包为 ESM 模块并自动优化输出体积

举个例子,如果我们想实现一个更复杂的带图标和插槽的按钮组件:

import { html, LitElement } from 'lit-html';

export class IconButton extends LitElement {
  static get properties() {
    return {
      icon: String,
      label: String
    };
  }

  render() {
    return html`
      <style>
        .icon-btn {
          display: flex;
          align-items: center;
          gap: 8px;
          padding: 10px 20px;
          border-radius: 4px;
          background-color: #28a745;
          color: white;
        }
      </style>
      <button class="icon-btn">
        ${this.icon ? html`<img src="${this.icon}" alt="">` : ''}
        <slot>${this.label}</slot>
      </button>
    `;
  }
}

customElements.define('icon-button', IconButton);

这种写法比纯原生的方式更易维护,也更容易与已有工具链集成。


效果总结:组件一致性、性能与可维护性的三重提升

1. 组件一致性显著提高

以前每个项目都有自己的“按钮”,现在整个公司都统一使用同一个 <my-button>,视觉风格和交互行为一致,极大提升了用户体验。

2. 性能优化空间更大

由于 Web Components 基于原生 DOM 操作,少了框架的中间层处理,性能表现更优。尤其是在某些老项目(如 jQuery 项目)中引入组件后,页面渲染速度反而比原先的复杂结构更流畅。

3. 维护成本大幅下降

统一组件库建立之后,升级某个功能只需要在一个地方改一次代码,npm 包更新后即可全平台生效。我们曾经有一次修复了一个点击穿透的 bug,影响了十几个页面,之前要手动修改,现在一个版本搞定。

4. 工程师之间的协作更顺畅

跨团队协作再也不需要讨论“用哪个框架”、“怎么传数据”,而是统一调用标准的 HTML 元素和属性。连测试同学都可以轻松理解组件行为。


经验分享:踩过的坑,以及值得坚持的地方

现代网页界面设计示例-1

Web Components 是个好东西,但也不是银弹。在这一年多的实践中,我们也遇到了不少问题,走了一些弯路,这里给大家一些实战建议。

1. 别指望它能马上替代 React/Vue

如果你正在做一个全新的中大型项目,还是建议用成熟框架。Web Components 更适合做的是:组件共享跨框架协作嵌入式微件(widget),而不是主框架。

2. 不要忽略 Shadow DOM 的局限性

虽然 Shadow DOM 提供了样式隔离,但它也有代价:

  • 无法被外部样式覆盖(除非你显式允许)
  • 调试不便,开发者工具显示的是 #shadow-root
  • 某些 CSS 特性受限,比如字体不能继承

因此,在设计组件时要考虑如何开放定制样式的能力,比如:

:host([primary]) button {
  background-color: blue;
}

或者对外暴露变量名让使用者自定义:

document.documentElement.style.setProperty('--my-theme-color', '#ff0000');

3. 事件系统需要特别注意

Web Components 的事件是原生 CustomEvent,在 React 或 Vue 项目中使用可能会有点不兼容。建议:

  • 使用 detail 传递数据
  • 监听事件时统一绑定到宿主节点
  • 对框架项目做适配封装(例如写一个 React Wrapper)

4. 工具链一定要跟上

我们一开始没有好好考虑工程化,结果组件库越来越难维护。后来我们做了几件事:

  • 引入 Rollup 打包成 ESM 模块
  • 用 Storybook 做组件演示文档
  • 自动化测试加入 Lit 测试工具(Lit Test)
  • 发布到私有 npm 私服,版本管理更规范

5. 不要忽视开发体验

Web Components 的一大劣势是开发体验不如框架友好。所以我们要主动弥补:

  • 给 IDE 装上 Web Components 插件
  • 在 VSCode 设置中开启标签提示
  • 使用 Prettier 插件格式化模板字符串
  • 写好 TypeScript 类型定义文件(.d.ts),提升智能提示体验

结语:回到原生,不止是为了“解耦”

写这篇文章的时候,我在想:为什么我们会对 Web Components 产生兴趣?其实本质是因为我们渴望一个真正通用、轻量、稳定的组件交付方式

在这个“万物皆组件”的时代,Web Components 给我们提供了一种不依赖框架、不绑架技术栈的可能性。它不像 React 那样“强控制”,也不像 Angular 那样“重配置”,而是以一种更“网页本色”的方式去解决问题。

当然,它并不完美,但也正因为如此,它留给了我们足够的自由去创造和探索。也许未来有一天,它会成为主流框架下的标配;也许不会,但它所代表的思想——面向原生、拥抱标准,始终是我们作为一名前端工程师应该坚守的方向。

希望这篇文章能为你打开一扇新的窗户。Web Components 并不是万能药,但在合适场景下,它可以是你组件化道路上不可或缺的利器。

如果你正在为组件复用发愁,不妨尝试一下,说不定会有意想不到的收获。

评论 0

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