Web Components:原生组件化开发的另一种可能

接口字段消失术
2025-06-25 13:39
阅读 426

开篇:一次重构让我重新认识了Web Components

开篇:一次重构让我重新认识了Web Components

最近我参与了一个大型前端项目的重构,项目最初是用Vue 2搭建的,随着业务复杂度的上升,团队成员也逐渐增多。在迭代过程中我们遇到了很多重复性的问题——组件复用困难、样式污染严重、不同技术栈之间的协作成本越来越高。为了解决这些问题,我们在调研多种方案后决定尝试一个之前一直“听过但没怎么用”的技术:Web Components

起初我也是持怀疑态度的,毕竟现在主流都是React、Vue这些成熟框架,Web Components看起来像是“古早”的产物。但在实际实践中发现,它其实在现代前端工程中依然有不错的适用场景,甚至在某些情况下比框架更有优势。

这篇文章我会结合我的真实项目经验,和你聊聊我是如何通过引入Web Components来解决实际问题的,以及过程中踩过的坑、收获的经验。


背景与挑战:为什么我们需要组件化?

背景与挑战:为什么我们需要组件化?

我们的系统是一个内部使用的数据看板平台,用户主要是产品经理和运营同事。页面结构相对固定,但交互逻辑较复杂。由于模块多,每个功能板块之间耦合较高,常常一个小改动就需要全局测试。

问题集中在以下几点:

  1. 组件复用难:同一个图表组件在多个地方使用,但由于封装方式不同,经常需要手动复制修改。
  2. 样式冲突严重:Vue单文件组件虽然用scoped避免部分样式污染,但有时候因为第三方库混用导致样式穿透问题仍然存在。
  3. 跨团队协作成本高:我们有两个小组并行开发不同的子模块,技术栈不统一(一组用Vue,一组想用React),导致整合时出现不少适配问题。
  4. 性能瓶颈初现:页面加载慢、首次渲染时间长,尤其是在低网速环境下。

当时我们就思考:有没有一种更通用、更轻量的组件化方式?既能保持灵活性,又能减少依赖框架的程度?

答案就是我们开始尝试的方向——Web Components。


技术选型与思路:为何选择Web Components?

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

  • Custom Elements:允许自定义HTML标签
  • Shadow DOM:提供隔离的DOM树和样式作用域
  • HTML Templates:通过<template><slot>实现内容分发

它的最大优点是无需任何框架依赖,可以直接在HTML中使用。这正好解决了我们两个小组用不同框架的问题。

我们最终决定将一些基础组件抽出来用Web Components重新实现。比如公共头部、表单项控件、弹窗、图表容器等。这些组件对性能敏感度不高,但强调稳定性和可复用性。


实践过程:从零开始构建一个Web Component

举个例子,我们要实现一个可搜索的选择器组件<searchable-select>

1. 定义Custom Element

class SearchableSelect extends HTMLElement {
  constructor() {
    super();
    this._shadowRoot = this.attachShadow({ mode: 'open' });
    this.options = [];
  }

  connectedCallback() {
    // 初始化组件UI
    this.render();
    this.loadOptions();
  }

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

  attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'placeholder') {
      this._placeholder = newValue;
      this.render();
    }
  }

  async loadOptions() {
    const resp = await fetch('/api/options');
    this.options = await resp.json();
    this.renderOptions();
  }

  render() {
    this._shadowRoot.innerHTML = `
      <style>
        .container { /* 样式隔离在这里 */
          border: 1px solid #ddd;
          padding: 8px;
          width: 200px;
          font-size: 14px;
        }
      </style>
      <div class="container">
        <input type="text" placeholder="${this._placeholder || '请输入'}" />
        <ul class="options"></ul>
      </div>
    `;
  }


![响应式布局概念图-1](https://code-guide.oss.shanghai.autogptai.club/common/file/download?name=date2025062513/221ae4f4-2c01-4a94-a1be-40f90929f8c7.jpg)


  renderOptions() {
    const ul = this._shadowRoot.querySelector('.options');
    ul.innerHTML = '';
    this.options.forEach(option => {
      const li = document.createElement('li');
      li.textContent = option.label;
      li.addEventListener('click', () => {
        this.dispatchEvent(new CustomEvent('select', {
          detail: option.value
        }));
      });
      ul.appendChild(li);
    });
  }
}

customElements.define('searchable-select', SearchableSelect);

这个组件可以在任意HTML文件中直接使用:

<searchable-select placeholder="请选择"></searchable-select>

<script>
  document.querySelector('searchable-select').addEventListener('select', e => {
    console.log('选择了:', e.detail);
  });
</script>

是不是很像写一个独立的JS插件?区别在于它是基于浏览器原生机制,并且可以嵌入到任意前端框架中。


遇到的坑和解决方案

1. 性能问题:频繁重渲染影响体验

刚开始的时候,每次输入框变化都触发过滤逻辑并重绘列表,结果在低端设备上卡顿明显。后来优化成节流处理 + 虚拟滚动(只渲染可视区域内的选项),才缓解问题。

const throttle = (fn, delay) => {
  let ticking = false;
  return function (...args) {
    if (!ticking) {
      setTimeout(() => {
        fn.apply(this, args);
        ticking = false;
      }, delay);
      ticking = true;
    }
  };
};

// 输入事件绑定
input.addEventListener('input', throttle((e) => {
  this.filterAndRender(e.target.value);
}, 200));

2. 样式泄露:Shadow DOM并不总是万能的

有时候外部CSS规则会穿透进来,尤其是字体、动画相关的全局样式。这时候可以通过给Shadow DOM包裹一层命名空间类名或者使用CSS变量来增强控制。

3. 第三方库兼容问题

有些第三方图标库或者工具函数依赖window或特定环境变量,在Web Components中引入需要做特殊处理。建议尽量使用无副作用的纯函数版本库,或者封装包装层。


实际效果与收益

经过几个月的逐步替换,我们主要获得了以下几个方面的提升:

  1. 组件解耦成功:Web Components让我们把核心功能模块抽象出来,供不同团队以统一的方式引用。
  2. 样式冲突减少:借助Shadow DOM的作用域隔离,大大减少了样式混乱问题。
  3. 性能表现良好:相比以前Vue组件动辄几十KB的初始加载,Web Component体积更小,启动更快。
  4. 维护成本下降:组件升级只需改一处即可全量生效,不再需要各个仓库分别同步更新。

更重要的是,这套架构为未来多技术栈共存奠定了良好的基础。


经验分享与建议

如果你也在考虑是否要用Web Components,这里是我总结的一些经验和建议:

✅ 适合的场景:

  • 基础UI组件库(按钮、表单、弹窗等)
  • 对性能要求中等,不需要频繁状态变更的功能
  • 多技术栈共存的项目(如同时使用Vue和React)

❌ 不太合适的场景:

  • 复杂的状态管理需求
  • 异步数据频繁更新的组件
  • 对兼容性要求极高(旧版IE等)

🛠️ 工具推荐:

  • 使用 LitElementStencil.js 可提高开发效率(它们封装了很多样板代码)
  • 用Rollup打包Web Component发布到npm非常方便
  • 推荐Chrome DevTools中的“Shadow DOM”面板调试样式隔离问题

💡 小技巧:

  • 组件通信尽量使用 CustomEvent,避免暴露过多API
  • 提供默认值,方便开发者理解props
  • Shadow DOM内尽量不要操作DOM,而是用数据驱动视图(可以用简单的模板引擎)

结语:别让技术限制思维边界

Web Components并不是新东西,但它却实实在在帮我们解决了眼前遇到的真实问题。它不是要替代Vue或React,而是作为它们之外的一个补充选项。

有时候我们会习惯性地觉得只有最主流的才是最好的,但实际上真正的好技术,是在合适的场景下发挥了应有的价值。

作为一名前端开发者,我一直坚信:没有银弹,只有不断寻找最适合当下情况的技术组合。而Web Components这次给了我一个新的视角,也让团队更加灵活高效。

希望这篇分享能给你带来一点启发,也欢迎你在评论区留言交流你的组件化开发心得。

评论 0

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