原生组件化,Web Components真香了

代码收容所
2026-01-28 08:44
阅读 1976

上周五晚上十一点,我还在公司死磕一个跨框架复用的UI组件。产品经理说“下周就要上线”,测试同学在群里@我“这个弹窗在React和Vue里表现不一致”,运维还在问“能不能减少bundle体积”。我盯着Mac屏幕右下角的时间,突然想起自己投出去的简历——再这么搞下去,怕是连跳槽面试都来不及准备。

作为一个天天调参炼丹的AI算法工程师,我其实对前端不算主业,但偏偏我们团队最近在做一套可视化分析平台,前端交互复杂得堪比TensorFlow Playground。为了快速迭代,我们用了React,但后端同事非要嵌入他们的老系统——那是套用jQuery写的古董级后台。结果就是,同一个日期选择器,我在React里写一遍,他们又在jQuery里抄一遍,样式还不统一。那一刻,我真的想砸电脑。

就在这时,我翻开了《Web Components实战》这本书——没错,就是那本被我扔在书架角落吃灰半年的纸质书。当时买它纯粹是因为打折,没想到现在成了救命稻草。书里第一章就点醒我:Web Components 是浏览器原生支持的组件化方案,不用任何框架,就能实现封装、复用和隔离。


为什么不用框架?因为太重了!

先别急着喷“都2024年了还搞原生?”,听我说完。我们团队最近上线了一个数据看板,里面嵌了十几个图表组件。用React打包后,光基础库就500KB+,而实际业务逻辑可能就100行JS。更离谱的是,后端同事想在他们的管理后台(纯静态HTML)里直接用我们的图表,结果webpack打包出来的玩意儿根本没法直接<script>引入。

这时候 Web Components 的优势就出来了:它基于浏览器原生能力,只依赖 HTML、CSS 和 JavaScript,不需要构建工具链,也不依赖 React/Vue/Angular。你写完一个组件,别人只需要一行 <script> + 一个自定义标签就能用。

而且,它天生支持 Shadow DOM,样式完全隔离。再也不用担心产品经理改个全局样式,你的组件就崩了——这种事我去年双11期间遇到过三次,每次都是凌晨三点被 PagerDuty 叫醒。


动手写一个“爬虫进度条”组件

举个接地气的例子。我们团队有个内部工具,用来监控爬虫任务状态。以前是用 Vue 写的,但运维想在 Shell 脚本跑完后直接生成一个 HTML 报告,里面显示进度。Vue 根本跑不起来,SSR 又太重。

于是,我用 Web Components 重写了一个 <crawler-progress> 组件:

// crawler-progress.js
class CrawlerProgress extends HTMLElement {
  constructor() {
    super();
    
    // 创建 Shadow DOM,实现样式隔离
    const shadow = this.attachShadow({ mode: 'open' });
    
    // 获取属性
    const total = this.getAttribute('total') || 100;
    const current = this.getAttribute('current') || 0;
    const percent = Math.min(100, Math.round((current / total) * 100));
    
    // 构建模板
    shadow.innerHTML = `
      <style>
        .progress-container {
          width: 100%;
          height: 20px;
          background: #f0f0f0;
          border-radius: 10px;
          overflow: hidden;
        }
        .progress-bar {
          height: 100%;
          background: linear-gradient(90deg, #4caf50, #8bc34a);
          width: ${percent}%;
          transition: width 0.3s ease;
        }
        .label {
          margin-top: 5px;
          font-size: 12px;
          color: #666;
        }
      </style>
      <div class="progress-container">
        <div class="progress-bar"></div>
      </div>
      <div class="label">已爬取 ${current} / ${total} 页</div>
    `;
  }
}

// 注册自定义元素
customElements.define('crawler-progress', CrawlerProgress);

使用起来超简单:

<!-- 在任何 HTML 页面里 -->
<script src="./crawler-progress.js"></script>

<crawler-progress total="1000" current="342"></crawler-progress>

连 jQuery 都不用!运维同学高兴得差点请我喝奶茶。关键是,这个组件只有 1.2KB(压缩后),比 Lodash 还小。


踩坑实录:兼容性、性能与调试

当然,天下没有免费的午餐。Web Components 也不是银弹。

第一坑:浏览器兼容性。虽然现代浏览器基本都支持了,但如果你的用户还在用 IE11(是的,有些国企内部系统还在用),那就得上 polyfill。不过好消息是,Google 的 webcomponents/polyfills 已经很成熟了,加个 script 就能跑。

第二坑:响应式更新。上面那个进度条组件有个问题:如果 current 属性变了,界面不会自动更新。因为 Web Components 默认不会监听属性变化。

解决方案是重写 attributeChangedCallback

static get observedAttributes() {
  return ['current', 'total']; // 监听这两个属性
}

attributeChangedCallback(name, oldValue, newValue) {
  if (oldValue !== newValue) {
    this.render(); // 手动触发重绘
  }
}

然后把 UI 渲染逻辑抽成 render() 方法。这有点像 Vue 的 watch,但更底层。

第三坑:调试困难。Shadow DOM 默认在 DevTools 里是折叠的,你得手动点开才能看到内部结构。好在 Chrome 最新版已经默认展开 Shadow Root 了。另外,Mac 上用 Safari 调试 Web Components 体验极差——所以我一般只用它开发,Windows 虚拟机里用 Edge 测试兼容性。


性能对比:轻量到离谱

为了说服团队全面采用,我做了个简单 benchmark。对比三种方式实现同一个卡片组件:

方案 Bundle 体积 首屏渲染时间 跨框架复用
React + JSX 120KB 180ms ❌ 需额外封装
Vue SFC 95KB 160ms ❌ 同上
Web Components 2.1KB 85ms ✅ 原生支持

数据是在 MacBook Pro M1 上测的,用 Lighthouse 跑的。可以看到,Web Components 不仅体积小一个数量级,首屏还快一倍——因为它不需要虚拟 DOM diff,直接操作真实 DOM。

当然,复杂交互场景下(比如列表滚动优化、状态管理),框架还是有优势。但对于展示型、工具型组件,Web Components 简直是降维打击


和爬虫、书籍、JavaScript 的奇妙联系

说到爬虫,其实 Web Components 对 SEO 也很友好。因为它是原生 HTML 元素,搜索引擎爬虫能直接解析内容(不像 SPA 那样需要 JS 执行)。我们有个公开的数据报告页面,用 Web Components 渲染表格,Google 索引速度比之前快了30%。

至于书籍,除了开头提到的《Web Components实战》,我还推荐 Eric Bidelman 的《Web Components in Action》。这位老哥是 Google 的 Web 开发布道师,书里全是实战案例,连如何用 Web Components 封装 D3.js 图表都讲了。

而 JavaScript?它才是 Web Components 的灵魂。虽然看起来只是写 HTML 和 CSS,但所有逻辑都靠 JS 驱动。比如你想让组件支持事件通信,可以这样:

// 组件内部
this.dispatchEvent(new CustomEvent('complete', {
  detail: { pages: this.current }
}));

// 外部使用
document.querySelector('crawler-progress')
  .addEventListener('complete', (e) => {
    console.log('爬虫完成!', e.detail.pages);
  });

这种基于 DOM 事件的通信机制,比 Vuex 或 Context API 更通用,也更容易被非前端同学理解。


我的建议:小步快跑,别all in

最后说点掏心窝子的话。作为正在准备跳槽的算法工程师,我学 Web Components 的初衷其实很功利:面试加分。现在很多大厂(比如阿里、字节)都在推微前端,而 Web Components 是微前端的底层技术之一。掌握它,至少能让你在“前端工程化”环节多聊十分钟。

但我不建议你把现有项目全盘重写。正确的姿势是:从边缘组件开始试点,比如加载动画、提示框、数据卡片。这些组件逻辑简单、复用率高,又不涉及复杂状态管理。

等你用熟了,再慢慢扩展到核心模块。我们团队现在就是混合架构:主应用用 React,但所有可复用的 UI 块都用 Web Components 实现。React 里通过 ref 操作组件属性,完美共存。


结语:原生的力量,被低估了

有时候,我们太依赖框架,反而忘了浏览器本身有多强大。Web Components 就像 JavaScript 的“裸金属编程”——没有抽象层,没有魔法,但足够快、足够稳、足够自由。

上周我把那个爬虫进度条组件提交到公司组件库,后端同事居然主动给我点了赞。他说:“终于不用看你们前端的 node_modules 了。”

那一刻,我觉得值了。

PS:如果你也在刷题准备跳槽,不妨花一小时试试 Web Components。说不定下次面试,你就能在“如何设计跨框架组件”这个问题上,惊艳面试官。毕竟,会调参的算法工程师很多,但既懂 PyTorch 又能手写 Shadow DOM 的,不多。

评论 0

最热最新
暂无评论
代码收容所Lv.1
0
影响力
0
文章
0
粉丝