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

马雨萱△
2025-12-17 15:54
阅读 596

大家好,我是个刚毕业的大专计算机应届生,在深圳一家做电商 SaaS 的小厂干前端。虽然学历被不少 HR 嘲过“简历筛不过”,但靠着自学 + 死磕 + GitHub 上攒的几个小项目,居然在去年秋招拿到了 offer。入职后天天和 Vue、React 打交道,直到上周五晚上加班到十一点,才真正对 Web Components 这个“冷门选手”有了点感觉。

事情是这样的:我们公司最近要给客户做一套可嵌入第三方网站的营销插件(比如弹窗、倒计时、抽奖组件),产品经理拍脑袋说:“这个必须能直接用 <script> 引入,不能依赖 React/Vue,人家后端可能就只会写 PHP!” 当时我就懵了——不靠框架怎么搞组件化?总不能真手搓 jQuery 吧?

为什么 Web Components 突然香了?

其实 Web Components 这个概念早在 2013 年就提出来了,但一直不温不火,大家都觉得“有 React 谁还用你啊”。但随着微前端、跨技术栈集成、低代码平台这些需求越来越多,它的优势反而凸显出来了:

  • 原生支持:不用打包、不用构建工具,浏览器天生就能跑
  • 框架无关:不管是 Vue、React、Angular,甚至纯 HTML 页面都能塞进去
  • 隔离性强:Shadow DOM 天然防样式污染,再也不怕 PM 说“你们前端改个样式把整个页面搞崩了”

对我们这种小团队来说,最爽的是——交付简单!以前给客户发个组件包,还得附带 package.jsonwebpack.config.js,现在直接丢一个 .js 文件,人家一行 <script src="my-widget.js"></script> 就完事了,后端同学都夸我们“终于懂人话了”。

动手试试:从零写个倒计时组件

我拿上周做的“双11倒计时”练手,目标很简单:传入一个截止时间,自动显示“X天X小时X分X秒”,还能自定义样式。

先看核心结构(别慌,代码真不多):

// countdown-widget.js
class CountdownWidget extends HTMLElement {
  constructor() {
    super();
    // 创建 Shadow DOM,隔离样式
    this.attachShadow({ mode: 'open' });
  }

  // 属性监听(类似 Vue 的 props)
  static get observedAttributes() {
    return ['endtime'];
  }

  // 属性变化回调
  attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'endtime') {
      this.render();
      this.startCountdown();
    }
  }

  render() {
    const endTime = new Date(this.getAttribute('endtime')).getTime();
    const now = Date.now();
    const diff = Math.max(0, endTime - now);

    const days = Math.floor(diff / (1000 * 60 * 60 * 24));
    const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
    const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
    const seconds = Math.floor((diff % (1000 * 60)) / 1000);

    this.shadowRoot.innerHTML = `
      <style>
        .countdown { font-family: Arial; color: #ff6b6b; }
        .number { display: inline-block; padding: 4px 8px; background: #f8f9fa; margin: 0 2px; border-radius: 4px; }
      </style>
      <div class="countdown">
        <span class="number">${days}</span>天
        <span class="number">${hours.toString().padStart(2, '0')}</span>时
        <span class="number">${minutes.toString().padStart(2, '0')}</span>分
        <span class="number">${seconds.toString().padStart(2, '0')}</span>秒
      </div>
    `;
  }

  startCountdown() {
    clearInterval(this.timer);
    this.timer = setInterval(() => {
      this.render();
    }, 1000);
  }

  disconnectedCallback() {
    // 组件卸载时清理定时器,防止内存泄漏
    clearInterval(this.timer);
  }
}

// 注册自定义元素
customElements.define('countdown-widget', CountdownWidget);

用起来就更简单了:

<countdown-widget endtime="2023-11-11T00:00:00Z"></countdown-widget>
<script src="./countdown-widget.js"></script>

上线那天,运维大哥居然没找我麻烦(要知道他上次因为 bundle.js 太大骂了我半小时),测试妹子也只报了一个 bug:“倒计时到 0 之后还在闪”。我一看,哦,Math.max(0, ...) 漏了,赶紧 hotfix。搞定那一刻,真的想请自己喝杯蜜雪冰城。

踩坑实录:别信 MDN 示例全是对的

虽然 Web Components 看着清爽,但实际开发还是有不少坑:

1. 样式穿透?不存在的!

Shadow DOM 的最大好处是样式隔离,但有时候你又希望外部能定制样式。这时候可以用 CSS Custom Properties(CSS 变量)

// 在组件内部
this.shadowRoot.innerHTML = `
  <style>
    .countdown {
      color: var(--countdown-color, #ff6b6b);
      font-size: var(--countdown-font-size, 16px);
    }
  </style>
  ...
`;

外部使用时:

<style>
  countdown-widget {
    --countdown-color: purple;
    --countdown-font-size: 20px;
  }
</style>
<countdown-widget endtime="..."></countdown-widget>

这招比 ::part()::slotted 兼容性好太多,IE 我们早就不伺候了(老板说客户最低要求 Chrome 70+)。

2. 生命周期别乱搞

connectedCallbackdisconnectedCallback 看似简单,但如果用户频繁挂载/卸载组件(比如 SPA 路由切换),很容易造成内存泄漏或重复渲染。一定要在 disconnectedCallback 里清理定时器、事件监听器。

3. 构建工具兼容性

我们项目还是用 Vue 2 写主站,但 Web Components 是独立构建的。我用 Vite 打包单个 JS 文件,配置贼简单:

// vite.config.widget.js
export default {
  build: {
    lib: {
      entry: 'src/countdown-widget.js',
      name: 'CountdownWidget',
      fileName: 'countdown-widget',
      formats: ['es']
    },
    rollupOptions: {
      output: {
        dir: 'dist/widget'
      }
    }
  }
}

执行 vite build --config vite.config.widget.js,直接输出干净的 ES Module,连 Babel 都省了(现代浏览器原生支持 class 语法)。

性能 & 兼容性:真能上生产吗?

我知道你在想:“这玩意儿性能行不行?老浏览器咋办?”

先说结论:现代项目完全可用

特性 Chrome Firefox Safari Edge
Custom Elements 54+ 63+ 10.1+ 79+
Shadow DOM 53+ 63+ 10+ 79+
Template 26+ 22+ 7.1+ 13+

数据来自 Can I Use。我们客户基本都是企业级 SaaS,浏览器版本普遍较新,IE?早就进博物馆了好吗!

至于性能,实测加载一个 5KB 的 Web Component,比引入整个 React(~40KB)快多了。而且无虚拟 DOM 开销,直接操作真实 DOM,对简单交互(比如按钮、表单、展示型组件)反而更高效。

对“简历焦虑”的一点思考

作为大专生,我一度特别焦虑:别人简历上写着“精通微前端架构”、“主导 Web Components 落地”,我连 Webpack 原理都说不清。但这次实战让我明白:技术不分高低,能解决问题的就是好技术

Web Components 虽然“古老”,但在特定场景下就是比框架更合适。而且掌握它,能让你在面试时多一个谈资——“我们用原生组件解决了跨框架集成问题”,HR 看了都觉得你“有架构思维”(虽然我只是个切图仔)。

更重要的是,它让我意识到:前端的核心不是 API 背诵,而是 理解浏览器本身。Shadow DOM、Custom Elements、Template,这些都是浏览器原生能力,学了永远不会过时。

最后:别被“新趋势”绑架

Web Components 不是银弹。如果你在做一个复杂的管理后台,该用 React 还是用 React;但如果是做可复用的 UI 插件、嵌入式小工具,它绝对是被低估的利器。

上周我把这个倒计时组件打包成 NPM 包(名字就叫 @mycompany/countdown-widget),结果隔壁组做 CRM 的后端大哥直接 npm install + <script type="module"> 引入,三分钟搞定。他发了个微信表情包:“前端终于干了件人事”。

那一刻,我觉得熬的夜都值了。


P.S. 如果你也想试试 Web Components,推荐两个工具:

  • Lit:轻量级库,帮你省掉模板字符串拼接的痛苦
  • VSCode 插件 "Custom Elements Manifest":支持 Web Components 的智能提示,装了它写 attributeChangedCallback 再也不怕拼错属性名

P.P.S. 简历上别写“精通 Web Components”,写“熟练应用 Web Components 解决跨技术栈组件复用问题”——听起来更像个人类 😅

评论 0

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