Web Components:在复杂项目中找到组件化的原生解法

监控面板盯梢人
2025-06-30 03:18
阅读 316

开篇背景

开篇背景

作为一名前端工程师,我在过去几年里参与了多个大型企业级 Web 项目的开发。随着 Vue、React 等现代框架的广泛普及,我们团队在开发中一直依赖这些框架进行组件化开发。但当一个新项目来了——目标是打造一套可在多平台通用、不依赖特定框架、轻量且易于集成的 UI 组件库时,我第一次认真思考起“如果不用 React 或 Vue,还能怎么做组件化开发?”。

答案指向了 Web Components。

遣使而来的挑战

遣使而来的挑战

这次我们要为公司搭建一套面向外部客户的 UI 设计系统,并提供一组可部署于任意技术栈下的基础组件(如按钮、输入框、表单控件等),同时要求:

  • 支持主流浏览器兼容性
  • 不依赖任何前端框架
  • 可被第三方轻松引入使用
  • 有统一的样式管理与交互逻辑
  • 性能要尽可能优

当时我们在调研过程中发现:

  1. 使用传统 jQuery 插件或纯 HTML/CSS 拼接的方式难以维护;
  2. 若坚持用框架实现,就必须让用户也安装对应框架(React、Vue)才能使用我们的组件,这明显不可行;
  3. 基于 Shadow DOM 封装 CSS 和行为似乎是个可行方向,于是把目光投向了 Web Components。

技术选型与方案制定

Web Components 并不是一个单一的技术,而是包括:

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

我们决定采用原生 Web Components API 结合简单的类库封装来构建组件系统。

🛠️ 小工具辅助选择:虽然原生 APIs 已经很强大,但我们最终引入了 Lit 这个轻量级类库,它基于 Web Components 标准构建,在性能、易读性和开发者体验上都有不错的表现。

实战演练:以“带提示文本的输入框”为例

场景说明

我们需要一个支持提示文字(placeholder)、错误状态(error state)、禁用状态(disabled)等特性的 <custom-input> 组件。它应该能够独立运行,无需依赖任何框架。

文件结构设计

components/
├── custom-input/
│   ├── index.js       ← 自定义元素注册
│   ├── input.css      ← 组件样式
│   └── input.html     ← HTML Template(可选)

主要代码实现

// components/custom-input/index.js
import { html, css, LitElement } from 'lit';
import styles from './input.css' assert { type: 'css' };

export class CustomInput extends LitElement {
  static properties = {
    placeholder: { type: String },
    error: { type: Boolean },
    disabled: { type: Boolean }
  };

  static styles = [styles];

  constructor() {
    super();
    this.placeholder = '';
    this.error = false;
    this.disabled = false;
  }

  render() {
    return html`
      <div class="custom-input-wrapper">
        <input 
          type="text" 
          .placeholder=${this.placeholder}
          ?disabled=${this.disabled}
          class="${this.error ? 'has-error' : ''}"
        />
        ${this.error ? html`<span class="error-message">请输入正确内容</span>` : ''}
      </div>
    `;
  }
}

customElements.define('custom-input', CustomInput);

对应的 input.css

.custom-input-wrapper {
  display: flex;
  flex-direction: column;
}

input {
  padding: 8px 10px;
  font-size: 14px;
  border: 1px solid #ccc;
  border-radius: 4px;
  outline: none;
}

input.has-error {
  border-color: #e74c3c;
}

.error-message {
  color: #e74c3c;
  font-size: 12px;
  margin-top: 4px;
}

然后直接在页面中调用即可:

<custom-input placeholder="输入你的名字" error disabled></custom-input>

这就是一个完整的无框架组件。

踩过的坑和应对经验

✅ 兼容性处理

尽管 Web Components 在 Chrome、Firefox 中已良好支持,但在部分老版本浏览器(比如 IE11)上完全无法运行。我们最终采取了以下策略:

  • 主推现代浏览器支持,放弃对 IE 的兼容
  • 对旧项目迁移场景,保留了 Vue 版本作为过渡方案
  • 引入 polyfill 仅针对少数遗留客户使用场景(通过动态加载判断浏览器类型)

❌ 动态样式传参问题

初期想通过属性传入不同主题色或者尺寸参数,却发现 CSS 变量在 shadow dom 内无法直接获取宿主元素的变量值。最后我们改用属性绑定 + JS 动态操作样式对象的方式解决。

this.style.setProperty('--input-bg', '#eee');

并在模板内 CSS 使用该变量:

:host {
  --input-bg: white;
}
input {
  background-color: var(--input-bg);
}

这样实现了从外层传递主题配置的能力。

⚠️ 开发调试小技巧

  • 使用 Chrome DevTools → Elements → 展开 #shadow-root 查看 shadow dom 内部结构。
  • 使用 Lit 提供的测试辅助包 lit/jest-dom 来编写单元测试。
  • 利用 VSCode 插件 Lit support for Visual Studio Code 提高开发效率。

最终落地效果

我们最终交付了一套包含 15+ 个常用控件的组件库,它们:

  • 完全脱离 React/Vue 独立运行
  • 可嵌入到任意前端框架内部使用
  • 包体大小控制在 60KB gzipped
  • 提供清晰文档并附有 Storybook 示例库
  • 支持无障碍访问(a11y)及响应式布局

上线后接入速度非常快,客户反馈也很正面。很多同事评价“终于有一个能像 HTML 原生标签一样用的 UI 库”。

心得总结与建议

如果你也在考虑是否使用 Web Components 技术:

✅ 建议尝试的场景

  • 构建跨框架可复用的 UI 组件
  • 微前端架构下需要隔离组件样式与行为
  • 想做渐进式升级(从 jQuery 逐步过渡)
  • 创建可公开发布的设计系统或开源组件库

🚫 暂时不推荐的情况

  • 项目周期短、急需快速交付,因生态和学习曲线仍存在门槛
  • 团队缺乏对原生 JavaScript 掌握能力
  • 要求高度动态交互(如动画/游戏/数据可视化)

🚀 我的一些建议

  1. 如果选择原生 API 编写,请熟悉 Shadow DOM 的生命周期方法(connectedCallback/disconnectedCallback);
  2. 推荐结合 Lit 使用,可以省去大量样板代码;
  3. 对样式封装要小心,避免污染全局环境;
  4. 善用自定义事件(dispatchEvent)来与父组件通信;
  5. 组件间通讯可以用 context api、pub-sub 方式替代 Redux;
  6. 一定要做单元测试,推荐使用 Playwright + Jest。

尾声:Web Components 正当时

虽然 React/Vue 还是现在开发中的主力,但我相信,组件化的本质其实是与框架无关的,而 Web Components 提供了一个真正标准化、原生支持的路径。

在这个追求极致灵活、轻量化、跨平台的时代,也许未来的组件标准就藏在每一个 <template>customElements.define() 的组合之中。


如果你也有类似的经验,欢迎留言交流~

评论 0

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