Web Components:原生组件化开发新趋势

一只会写码的猫
2025-12-16 01:18
阅读 660

上周五晚上 10 点,实验室里只剩我和隔壁工位的小王还在肝项目。他盯着控制台报错一脸懵:“这玩意儿怎么又把 Shadow DOM 的样式给吃了?” 我瞥了一眼,笑出声——这不就是我们最近在折腾的 Web Components 嘛。

我是杭电软件工程研二的学生,目前在实验室跟着导师做企业级中后台系统重构。杭州这边,阿里网易扎堆,机会不少,但卷得也厉害。为了能在秋招前多点拿得出手的项目经验,我主动揽下了“前端微前端架构升级”这个活儿。结果一上来就被领导扔了个需求:“能不能搞个跨框架复用的 UI 组件库?别再让 React 和 Vue 团队天天吵架了。”

行吧,被逼上梁山,那就学 Web Components 吧。


为啥是 Web Components?

说实话,之前我对 Web Components 的印象还停留在“浏览器原生支持、但没人用”的阶段。毕竟在 React/Vue 主导的世界里,谁还去手写 customElements.define 啊?直到去年双11期间,我们给某大厂做的运营活动页因为用了太多第三方 UI 库,首屏加载直接飙到 3.2s,产品总监差点冲进机房拔网线。

后来复盘时发现,问题核心在于:每个框架都有自己的组件模型,一旦要跨团队协作,就得重复造轮子,或者搞一堆胶水代码。而 Web Components 是浏览器原生支持的组件化方案,天生具备框架无关性——React 能用,Vue 能用,连老掉牙的 jQuery 项目都能塞进去。

最关键的是:它不用打包!不用编译!开箱即用!

(当然,这只是理想状态。现实是……后面再说。)


初体验:三件套走起

Web Components 核心就仨 API:

  • Custom Elements:自定义标签
  • Shadow DOM:样式和 DOM 隔离
  • HTML Templates:声明式模板

举个最简单的例子,我想做个 <my-button>

class MyButton extends HTMLElement {
  constructor() {
    super();
    // 创建 Shadow Root
    const shadow = this.attachShadow({ mode: 'open' });
    
    // 模板
    const template = document.createElement('template');
    template.innerHTML = `
      <style>
        button {
          background: #4CAF50;
          color: white;
          border: none;
          padding: 8px 16px;
          border-radius: 4px;
          cursor: pointer;
        }
        button:hover {
          opacity: 0.9;
        }
      </style>
      <button><slot></slot></button>
    `;
    
    // 克隆模板并挂载
    shadow.appendChild(template.content.cloneNode(true));
  }
}

// 注册自定义元素
customElements.define('my-button', MyButton);

然后在 HTML 里直接用:

<my-button>点我啊!</my-button>

搞定!样式不会污染全局,DOM 结构被 Shadow DOM 隔离,还能通过 <slot> 插入内容。那一刻我仿佛看到了前端组件化的终极形态——真正的“一次编写,到处运行”


踩坑实录:理想很丰满,现实很骨感

然而,当我兴冲冲把这个按钮集成到我们现有的 Vue 项目时,测试小姐姐发来消息:“你这按钮点了没反应啊?”

一查才发现:Vue 默认不会监听 Custom Element 的属性变化!比如我想加个 disabled 属性:

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

attributeChangedCallback(name, oldValue, newValue) {
  if (name === 'disabled') {
    const button = this.shadowRoot.querySelector('button');
    button.disabled = newValue !== null;
  }
}

但在 Vue 里写 <my-button :disabled="isDisabled">,属性根本不会同步到 Custom Element 上。最后靠 v-bind="$attrs" 才勉强解决。

更惨的是兼容性问题。虽然现代浏览器基本都支持了,但Safari 对 Shadow DOM 的 CSS 变量支持有坑,IE?别想了,直接放弃。

还有性能问题。每个 Web Component 都会创建一个 Shadow Root,内存开销比普通 div 大不少。我们压测时发现,页面渲染 1000 个 <my-button>,FPS 直接掉到 40 以下。最后不得不加虚拟滚动 + 动态加载。


开发心得:稳定 vs 新潮的平衡术

在实验室做项目,导师总说:“新技术可以玩,但上线必须稳。” 所以我们最终采用了一个折中方案:

  • 内部工具类组件(比如数据看板、配置面板):大胆用 Web Components,享受原生隔离和跨框架优势;
  • 用户高频交互组件(比如表单、列表):还是用 Vue + TypeScript,保证性能和开发体验;
  • 对外交付的 SDK:把核心逻辑封装成 Web Components,让客户无论用什么技术栈都能集成。

顺便安利一个神器:Lit。这是 Google 出的轻量级 Web Components 库,用装饰器语法写起来像 Vue 3 的 Composition API,还自带响应式更新。代码量少一半,心智负担小很多。

import { LitElement, html, customElement, property } from 'lit';

@customElement('my-button')
export class MyButton extends LitElement {
  @property({ type: Boolean }) disabled = false;

  render() {
    return html`
      <style>
        /* ... */
      </style>
      <button ?disabled=${this.disabled}><slot></slot></button>
    `;
  }
}

简直不要太爽!


效果如何?值不值得投入?

经过三个月的实战,我们的 Web Components 组件库已经支撑了 5 个内部系统,首屏加载时间平均减少 1.1s,而且前端团队再也不用为“你用 React 我用 Vue”吵到运维介入了。

更重要的是,它让我重新思考了“组件”的本质。以前总觉得组件就是 UI + 逻辑的封装,但现在明白:真正的组件应该是“自治的、可组合的、与环境解耦的”。Web Components 虽然不够完美,但它指明了一个方向——回归浏览器原生能力,减少对框架的依赖。


写在最后:代码人生,不止 CRUD

有时候想想,我们这代程序员真的很幸运。十年前还在为 IE6 兼容掉头发,现在却能站在 Web Components、WebAssembly、WebGPU 这些前沿技术的门口张望。

当然,现实很骨感:产品经理明天又要改需求,测试说线上有个偶现 Bug,运维催着上线……但正是这些琐碎日常中的技术探索,才让“代码人生”有点意思。

所以,如果你也在杭州,也在卷大厂 offer,不妨试试 Web Components。它可能不是银弹,但绝对是一把值得放进工具箱的瑞士军刀。

毕竟,技术人的浪漫,就是在 deadline 前夜,还能写出一行让自己骄傲的代码。


附:主流方案对比(实验室实测)

方案 跨框架支持 样式隔离 性能 学习成本 适合场景
React Component ⭐⭐⭐⭐ ⭐⭐ 纯 React 项目
Vue SFC ✅(scoped) ⭐⭐⭐⭐⭐ 纯 Vue 项目
Web Components ✅(Shadow DOM) ⭐⭐ ⭐⭐⭐ 跨框架/SDK/嵌入式
Lit(基于 WC) ⭐⭐⭐ ⭐⭐ 轻量级 WC 开发

数据来源:实验室 2024 Q2 前端技术选型报告(其实就是我和小王瞎测的)


P.S. 如果你也在折腾 Web Components,欢迎交流!我的 GitHub 仓库里有完整示例(带踩坑注释),链接就不贴了——毕竟导师说“别在博客里打广告”。

评论 0

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