Web Components:在组件化开发中的新探索

技术碎碎念
2025-06-17 18:42
阅读 473

开篇:一个老前端的自省与尝试

开篇:一个老前端的自省与尝试

我是张凯,5年多的前端开发老兵。从早期 jQuery 一把梭,到 Angular、React、Vue 三大框架轮流上阵,再到如今对技术选型越来越理性的我来说,每一次技术变迁都带来不同的思考。

最近两年,在一次大型重构项目中,我们团队面临一个现实问题:现有组件库维护成本越来越高,复用性却逐渐变差。尤其是当我们想要将某些核心组件跨项目复用时,因为依赖太多业务逻辑和框架特性,总是“拆不干净”。就在那时,我开始重新审视 Web Components 这个概念,并逐步将其引入项目实践。

今天,我想分享的就是这一段经历——关于为什么选择 Web Components、遇到了哪些坑,以及它到底能为我们的工作带来什么。


问题描述:组件的可复用之痛

问题描述:组件的可复用之痛

这个故事要从一个需求说起。

我们的公司有多个产品线,各自基于 Vue 和 React 搭建,其中有一个非常常见的 UI 组件叫 FilterBar,也就是筛选栏,每个页面基本都会出现。最初是基于 Vue 实现的,封装得还不错,也做了一些抽象。但随着业务复杂度上升,它慢慢变得臃肿:

  • 外部样式依赖越来越多(scoped vs. global 样式冲突)
  • 需要注入大量的状态管理代码
  • 跨项目使用时必须连带引入整个 Vue 实例或者一堆公共方法
  • 最关键的是:换框架就彻底歇菜 —— 我们想把它的能力复用到 React 项目里,根本无从下手。

这并不是个例。像按钮、弹窗、表单控件这类基础组件都在各个项目中被重复实现过至少两次。每次迁移都要写一遍适配层。我们意识到,这种高度耦合框架的设计方式,限制了组件的真正生命力。

于是我们在技术方案讨论会上提出了一个问题:“有没有一种办法,能让这些基础组件不再依附于具体的技术栈?”

答案就是 Web Components。


解决方案:Web Components 来了

解决方案:Web Components 来了

初识 Web Components

Web Components 是一组浏览器原生支持的标准,主要包括:

  1. Custom Elements(自定义元素)
  2. Shadow DOM(影子 DOM)
  3. HTML Templates(模板标签)
  4. ES Modules 加载机制

简单来说,你可以通过 JavaScript 创建一个全新的 HTML 元素,比如 <my-button>,它有自己的结构、样式和行为,完全隔离于外部环境。

听起来很理想,那怎么落地呢?

为什么这次决定尝试 Web Components?

主要有几个动因:

  • 技术解耦:无需依赖 React 或 Vue 的语法体系
  • 样式隔离:Shadow DOM 可以天然防止样式污染
  • 渐进集成:可以在任何项目中直接使用
  • 生态兼容性:主流现代浏览器均已原生支持(甚至 IE11 + polyfill 也能玩)

当然,最大的吸引力在于:它是一个标准方案,不需要我们再搞一套复杂的封装机制来模拟组件系统。


代码实践:从零开始打造一个真正的 Web Component

代码实践:从零开始打造一个真正的 Web Component

先来看一个小案例 —— 我们来写一个最简单的按钮组件 <custom-button>

<!-- index.html -->
<custom-button color="blue">点击我</custom-button>

接下来是定义这个组件的 JS 文件:

// custom-button.js
class CustomButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    
    const template = document.createElement('template');
    template.innerHTML = `
      <style>
        button {
          padding: 10px 20px;
          border-radius: 5px;
          font-size: 16px;
          cursor: pointer;
          background-color: ${this.getAttribute('color') || 'gray'};
          color: white;
          border: none;
        }
      </style>
      <button><slot>默认文本</slot></button>
    `;
    
    this.shadowRoot.appendChild(template.content.cloneNode(true));
  }
}

customElements.define('custom-button', CustomButton);

然后在 HTML 中引入:

<script type="module" src="./custom-button.js"></script>

这样你就能像使用普通标签一样使用 <custom-button>,而且:

  • 它内部的样式完全独立,不会影响外部样式
  • 你可以传入属性(如 color),也可以通过插槽传内容
  • 它可以运行在任何框架的环境中,甚至静态 HTML 页面也能使用!

踩坑经验:理想丰满,现实骨感

你以为事情就这样结束了?并没有。实际开发过程中我们踩了不少坑。

坑一:事件绑定的困扰

最开始我们以为只要监听 Shadow DOM 内部的节点就可以了,但遇到一个问题:当用户点击按钮时,父级组件拿不到事件!因为在 shadow root 里面触发的事件,默认不会冒泡到外层。

解决方法:手动派发 Event 并设置 bubbles: true

const btn = this.shadowRoot.querySelector('button');
btn.addEventListener('click', () => {
  this.dispatchEvent(new CustomEvent('click', { detail: '来自 web component 的点击事件', bubbles: true }));
});

这样外面就可以通过 @clickaddEventListener 来监听了。


坑二:生命周期处理不够智能

不同于 Vue 或 React 的组件生命周期管理,Web Components 在这方面完全是“裸奔”。比如我们要监听属性变化怎么办?

需要自己手动观察属性:

static get observedAttributes() {
  return ['color'];
}

attributeChangedCallback(name, oldVal, newVal) {
  if (name === 'color') {
    const button = this.shadowRoot.querySelector('button');
    button.style.backgroundColor = newVal;
  }
}

虽然麻烦一点,但也给了我们更细粒度的控制权限。


坏掉的小插曲

有一次在开发一个比较复杂的下拉框组件时,发现样式在不同页面上偶尔会错乱。排查了很久才发现是因为没有正确使用 adoptedStyleSheet 来共享全局样式。

后来改用 CSSStyleSheet + adoptedStyleSheets 的方式来统一管理公共样式,才避免了这个问题。


构建优化的问题

刚开始的时候我们只是在 HTML 页面中用 <script type="module"> 直接引用 ES Module,但项目一旦复杂起来就很慢。所以后来我们引入了构建工具。

我们最终选择的方式是:

  1. 使用 Vite 构建工具打包 Web Components
  2. 对于生产构建,用 Rollup 打成 ESM 和 UMD 版本
  3. 在不同项目中作为 NPM 包安装使用

配置文件简化如下(Vite):

// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],
  build: {
    target: 'es2015',
    outDir: 'dist',
    lib: {
      entry: './src/index.js',
      name: 'MyComponents',
      fileName: (format) => `my-components.${format}.js`
    },
    rollupOptions: {
      external: ['react', 'vue'],
      output: {
        globals: {
          react: 'React',
          vue: 'Vue'
        }
      }
    }
  }
});

效果总结:一次成功的重构尝试

回过头来看这个决策,确实让我们团队受益不少。

收益点一:跨项目复用效率大幅提升

以前我们要在两个框架中重复实现一个组件,现在只需要写一份 Web Components 就可以同时服务 React、Vue 甚至是 jQuery 项目。组件本身也不再需要依赖某个构建体系,真正做到了“随取随用”。

收益点二:样式隔离让协作更容易

大家都知道,样式冲突是最让人头疼的问题之一。Shadow DOM 提供了一种近乎完美的解决方案,我们再也不怕第三方组件污染样式了。

收益点三:维护成本下降

由于脱离了框架,我们不再需要为不同项目升级 Vue 或 React 而重复更新组件库。更新只需关注功能本身即可。


经验分享:给你的几点建议

如果你也在考虑是否采用 Web Components,我可以给出以下几个建议:

✅ 不要用 Web Components 替代全部组件

Web Components 是轻量级、通用性优先的选择。对于业务组件,尤其涉及复杂状态管理和业务逻辑的,还是建议留在框架内。Web Components 更适合做 UI 层基础组件库

✅ 推荐配合构建工具一起使用

直接用 script 引入没问题,但如果是中大型项目,推荐配合 Vite 或 Rollup 打包构建,能更好管理模块依赖和输出格式。

✅ 浏览器兼容性要评估清楚

虽然现代浏览器已经广泛支持,但在低版本浏览器上仍需 polyfill(如 @webcomponents/webcomponentjs)。我们为了兼容 IE11 也曾被迫加上 polyfill,性能有一定影响。

✅ 注意交互细节

别忘了这是面向用户的组件。哪怕是个按钮,也要注意 accessibility、focus 状态、键盘操作等细节。


技术趋势:Web Components 正在崛起

其实早在几年前,Web Components 就已经初露锋芒。如今,越来越多的大厂开始采用 Web Components 来打造跨框架组件库,比如 Salesforce 的 Lightning Design System、Google 的 Material Web Components,还有 Adobe Spectrum 等。

而社区也在不断涌现新的工具链,例如 StencilJS、LitElement 等,它们进一步降低了开发门槛,甚至允许你用类 React 的方式来写组件逻辑。

更重要的是,它背后不是某一家公司的利益驱动,而是 W3C 主导的开放标准。这比任何一家公司主导的框架更具有生命力。


结语:写给未来自己的提醒

响应式布局概念图-1

Web Components 并非万能钥匙,但它提供了一种“回归本源”的思路。

在这次项目实践中,我收获了很多,也明白了一个道理:技术选型从来都不是追求最新或最流行,而是要看它能不能真的解决问题。

Web Components 让我重新认识了“组件”这件事的本质——它不是一个语法糖,不是一个框架功能,而是应该属于 Web 本身的语言能力。

希望这篇文章能为你带来一些启发,也欢迎你在评论区留言交流想法或实践经验。一起成长,一起进步。


文末彩蛋:如果需要完整的组件示例代码或工具链配置,欢迎私信我获取 GitHub 示例仓库链接。

评论 0

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