从Vue转战Web Components:我在实际项目中的一次“原生”重构实践

一帆风顺
2025-06-13 07:26
阅读 354

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

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

作为一名前端开发者,过去几年我一直在使用像 Vue 和 React 这样的主流框架构建企业级应用。直到去年,我们接到一个新项目需求——开发一套可以被多个不同技术栈系统复用的组件库。我们考虑过 Monorepo 模式、微前端架构、甚至尝试封装成 npm 包引入,但始终觉得不够灵活。

后来,我突然想到之前关注过的 Web Components 技术,它是一种基于浏览器原生能力的组件化方案,理论上不依赖任何框架。于是抱着试试看的心态,我们决定在新项目中尝试全面采用 Web Components 来构建 UI 组件库。

这篇文章就聊聊我在这个项目中的实战经验:为什么选择 Web Components,遇到了哪些挑战,怎么解决,又有哪些收获和建议


问题描述:跨团队协作下的组件复用难题

问题描述:跨团队协作下的组件复用难题

前端性能优化图表-2

项目的背景是为公司旗下的多个业务系统提供统一的可视化展示组件(如图表组件、数据面板等),要求:

  • 支持 Vue、React、甚至 jQuery 等多种技术栈接入
  • 不依赖特定打包工具或运行时环境
  • 实现主题可配置、UI 高度可定制
  • 必须具备良好的性能和兼容性

传统做法一般是:

  1. 用某个框架开发组件库
  2. 打包发布成 npm 包供其他项目引入
  3. 各个项目再按各自方式集成

但这种模式有几个痛点:

  • 如果项目主应用不是同一框架,就得重新实现一遍组件
  • 需要引入额外编译插件,容易引发版本冲突
  • UI 主题和样式管理复杂,难以统一

这些痛点在实际协作中确实困扰了我们很久。我们需要一种真正“无侵入”的组件共享方式。


解决方案:为什么不选框架?选 Web Components!

解决方案:为什么不选框架?选 Web Components!

最终,我们选择了基于 原生 Web Components + LitElement 的开发模式。

为什么不是直接手写 vanilla web components?因为我们还是希望有些现代特性的支持,比如响应式状态绑定、模板语法等。而 LitElement(后升级到 Lit) 是一个轻量、高性能、社区活跃的选择,能够让我们快速上手并保持代码的整洁。

我们的技术栈如下:

  • 基于 Lit 构建组件类库
  • 使用 Shadow DOM 实现样式的隔离
  • 自定义元素名全部以 my-component-xxx 形式命名
  • 主应用通过动态导入方式注册并使用组件
  • 样式通过 CSS Variables 实现主题定制

整个工程目录结构大致如下:

web-components/
├── packages/
│   ├── data-panel/
│   ├── chart-component/
│   └── shared-utils/
├── demo-app/            // 示例项目,用于调试
├── rollup.config.js     // 构建配置
└── index.html           // 入口页面

代码实践:如何创建一个可复用的组件?

代码实践:如何创建一个可复用的组件?

举个最简单的例子,我们先来看一个“按钮组件”的实现:

// src/my-button.js
import { LitElement, html, css } from 'lit';

export class MyButton extends LitElement {
  static styles = css`
    button {
      background-color: var(--my-button-bg, #007bff);
      color: white;
      padding: 8px 16px;
      border-radius: 4px;
      cursor: pointer;
    }
  `;

  render() {
    return html`<button><slot></slot></button>`;
  }
}

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

在 HTML 中这样使用:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>My Button Demo</title>
  <script type="module" src="/my-button.js"></script>
  <style>
    :root {
      --my-button-bg: #dc3545;
    }
  </style>
</head>
<body>
  <my-button>提交表单</my-button>
</body>
</html>

是不是很简单?我们通过这种方式实现了:

  • 组件的自定义标签 <my-button>
  • 默认样式与可定制的主题变量配合
  • 内部使用 Shadow DOM 隔离,防止样式污染
  • 完全脱离任何框架依赖,任意技术栈都能使用

踩坑经验:开发路上遇到的那些事

虽然整体过程相对顺利,但在实践中我们也踩了不少坑,这里分享几个重点:

✨ Shadow DOM 的样式穿透问题

一开始我们以为用了 Shadow DOM 就完全隔绝样式了,结果在某些情况下发现外部全局样式还是会“泄漏”进来。经过研究发现,这是因为:

  • 使用 @extend@include 这些 Sass 功能生成的样式不会自动注入 Shadow DOM
  • 第三方字体图标库(如 FontAwesome)也无法在 Shadow DOM 内加载

解决办法:

  1. 使用 adoptedStyleSheets API 显式挂载样式表到 Shadow Root
  2. 对于全局字体,在定义组件时手动注入 <link> 标签

🧠 如何高效调试 Web Component?

由于没有 Vue Devtools、React Developer Tools 这种专门的调试工具,调试起来会有点吃力。我的技巧是:

  • 在控制台打印 shadowRoot:console.log(this.shadowRoot)
  • 使用 Chrome 的 Elements 面板查看 Shadow DOM 结构
  • 给组件加临时属性,方便标记状态,例如:<my-component debug-mode></my-component>

🔁 在 Vue / React 中使用 Web Components 的注意事项

虽然 Web Components 是原生的,但想在 Vue 或 React 中使用,仍然要注意一些细节:

在 Vue 中使用:

<template>
  <div>
    <my-button @click="handleClick">点击我</my-button>
  </div>
</template>

<script>
export default {
  mounted() {
    import('path-to/web-components/my-button');
  },
}
</script>

注意:要在组件挂载后再动态引入脚本,避免加载顺序出错。

在 React 中使用:

function MyComponentWrapper() {
  useEffect(() => {
    import('path-to/web-components/my-button');
  }, []);

  return (
    <my-button onClick={() => alert('clicked!')}>点我</my-button>
  );
}

⚠️ 注意大小写问题,React 对自定义元素标签名的大小写敏感,一定要用小写命名。


效果总结:这套方案带来了哪些收益?

经过半年多的实践,我们可以明显感受到这套基于 Web Components 的组件体系带来的优势:

真正的“一次编写,到处可用”
我们在五个不同的技术栈项目中成功复用了这套组件库,包括 Vue3 项目、jQuery 老项目、Next.js 应用等。

更清晰的边界划分与职责分离
每个组件都是独立的 HTML 元素,功能内聚、API 明确,极大减少了沟通成本。

构建速度提升,依赖减少
不再需要引入复杂的 Babel、Webpack 插件链。我们的 Rollup 构建时间几乎缩短了一半。

主题定制更加灵活
CSS 变量机制使得修改颜色、字号变得非常容易,甚至可以在运行时动态切换主题。


经验分享:给准备入门的朋友几点建议

如果你也打算尝试 Web Components,以下是我在工作中总结的一些实用经验,希望能帮到你:

1. 别想着“一步到位”,从小组件开始练手

别一开始就试图封装一个庞大的组件库。建议先拿一些简单通用的组件练手,比如按钮、卡片、输入框等,熟悉基本套路后再逐步深入。

2. 用 Lit 或 Stencil 提升开发效率

虽然可以直接用 JS 创建 Custom Element,但我建议结合 Lit 或 Stencil 来开发,能节省大量模板代码。Lit 更轻量,适合组件不多的项目;Stencil 更适用于大型组件库构建。

3. 注意浏览器兼容性问题

目前主流浏览器都支持 Web Components(Chrome、Edge、Firefox、Safari 也都已支持多年),但为了兼容老旧 IE,可以考虑加上 polyfill:

<!-- 引入 polyfill -->
<script src="https://unpkg.com/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>

4. 多利用浏览器原生特性

Web Components 的一大魅力就在于它使用的是浏览器原生特性,比如:

  • Shadow DOM:样式隔离
  • Custom Elements:自定义标签
  • HTML Templates:静态 HTML 片段声明
  • CSS Custom Properties:主题变量支持

合理使用它们,能让你的组件体验更好。

5. 保持开放心态,组合使用而非替换现有框架

Web Components 并不是要取代 Vue、React,而是一种互补。你可以把它作为“公共组件层”来建设,让各子项目都能调用。


写在最后:技术只是手段,解决问题才是目的

响应式布局概念图-1

在写这篇文章的时候,我也在反思自己这些年对技术趋势的理解。过去我们追求各种高大上的框架,但现在回头看看,有时回归原生、简化结构反而是更高效率的方式。

Web Components 不是银弹,但它的确提供了一个轻量、跨平台、可持续维护的组件化思路。如果你的项目也有类似的组件共享需求,不妨一试。

当然,这只是一个项目的探索经验,不代表所有场景都适用。欢迎你在评论区和我交流你的想法或者实践经验。


如果你喜欢这类“真实开发经验”分享的文章,欢迎关注我。我会继续写更多有温度、有细节的技术文章,带你一起走过每一个真实的编码旅程 💻✨

评论 0

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