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

哈希表少年
2025-12-17 21:47
阅读 1963

上周五晚上十一点半,我合上笔记本电脑,泡了杯枸杞茶,终于把新项目里那个“神奇的悬浮反馈按钮”搞定了。没加班、没拉会、没被产品经理半夜钉钉轰炸——这在我们国企研发部简直是奢侈。坐在我对面的老王探头问:“你这按钮咋做到跨框架还能动画丝滑的?”我嘿嘿一笑:“Web Components,原生组件,香得很。”

是的,我就是那个在北京通勤一小时、白天喝茶看报、深夜写代码效率爆表的国企程序员。别笑,我们部门虽然节奏慢点,但技术热情可没落下。去年双11期间,隔壁电商组被 React 升级搞到崩溃,而我们却悄悄用 Web Components 把几个老系统里的交互模块重构了一遍——没动一行业务逻辑,UI 却焕然一新。

为啥要折腾这个“古董”?

说起来有点尴尬。我们系统里既有 Vue 2 的遗产,又有 AngularJS 的“活化石”,最近还掺了点 React 新模块。产品经理美其名曰“技术多元化”,实则让前端维护成了“缝合怪”。最离谱的是,同一个 Toast 组件,在三个框架里写了三遍,样式还不统一。测试同事每次提 Bug 都要备注“仅限 React 页面复现”,运维大哥看到 bundle size 直接血压飙升。

更惨的是,有一次我想复用一个带 Lottie 动画的加载指示器,结果光是处理框架差异就熬了两个通宵。当时真的想砸电脑——不是因为代码难写,而是明明功能一样,却要在不同地方重复造轮子。

就在那晚,我在 Stack Overflow 刷到一篇关于 Web Components 的旧帖,突然灵光一闪:浏览器原生支持的组件,不就是跨框架的终极解法吗?

原生组件真能打?

先说结论:能,而且打得挺稳

Web Components 不是什么新技术,它其实由三部分组成:

  • Custom Elements:自定义 HTML 标签
  • Shadow DOM:封装样式和 DOM,避免污染
  • HTML Templates:声明式模板

听起来很底层?但正因为“原生”,它不需要任何构建工具就能跑。Chrome、Edge、Firefox、Safari 全家桶都支持(IE?拜拜了您内)。更重要的是——它和 React、Vue 完全不冲突

举个例子,我写了个 <fancy-tooltip> 组件:

class FancyTooltip extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = `
      <style>
        .tooltip { 
          background: #333; 
          color: white; 
          padding: 8px 12px;
          border-radius: 4px;
          animation: fadeIn 0.3s ease;
        }
        @keyframes fadeIn {
          from { opacity: 0; transform: translateY(-5px); }
          to { opacity: 1; transform: translateY(0); }
        }
      </style>
      <div class="tooltip">
        <slot></slot>
      </div>
    `;
  }
}

customElements.define('fancy-tooltip', FancyTooltip);

然后在 React 项目里直接用:

function App() {
  return (
    <div>
      <button>
        悬浮提示
        <fancy-tooltip>我是跨框架 tooltip!</fancy-tooltip>
      </button>
    </div>
  );
}

不需要 import,不需要 HOC,不需要 context bridge。React 只把它当普通 DOM 节点渲染,内部逻辑完全由 Web Component 自己管理。这意味着:一次编写,到处使用

和 React 到底啥关系?

很多人一听“组件化”,第一反应就是 React。确实,React 的组件模型影响了一代人。但 React 是“运行时组件”,依赖虚拟 DOM 和 reconciliation;而 Web Components 是“平台级组件”,直接由浏览器理解。

对比项 React 组件 Web Components
依赖 需要 React 运行时 浏览器原生支持
跨框架 ❌(需额外桥接) ✅(天然支持)
样式隔离 靠 CSS-in-JS 或命名约定 Shadow DOM 天然隔离
性能 虚拟 DOM diff 开销 直接操作真实 DOM
学习成本 高(JSX、hooks 等) 中(需理解 DOM API)

我不是说 React 不好——我们内部新项目还在用 React。但当你需要共享 UI 原子能力(比如按钮、弹窗、动画控件)时,Web Components 更像是“基础设施”。

实战踩坑记

当然,理想很丰满,现实有坑。

第一个坑:属性传递。React 默认把 props 当作 attributes 传给 DOM,但 Web Components 期望的是 properties。比如你想传一个对象 {theme: 'dark'},直接写 <my-comp config={{theme:'dark'}} /> 是不行的,因为 attribute 只能是字符串。

解决办法?要么用 ref 手动赋值:

const ref = useRef();
useEffect(() => {
  ref.current.config = { theme: 'dark' };
}, []);
return <my-comp ref={ref} />;

要么封装一层 React Wrapper,把 props 转成 properties。我们团队现在用 reactify-wc 这类小工具自动处理,省心不少。

第二个坑:事件冒泡。Web Components 内部 dispatch 的事件,默认不会穿透 Shadow DOM。如果你在 React 里监听 <my-button onClick={...} />,可能收不到 click。

解决方案是在组件内部显式设置 composed: true

this.dispatchEvent(new CustomEvent('myclick', {
  bubbles: true,
  composed: true // 关键!
}));

第三个坑:开发体验。没有热更新、没有 TypeScript 智能提示、调试时 Shadow DOM 要手动展开……这些确实不如现代框架舒服。但我们用 Lit(一个轻量级 Web Components 库)+ Vite 搞定大部分问题。Lit 提供类似 React 的响应式更新,Vite 支持原生 ES Module,开发体验瞬间拉满。

效果如何?

上线三个月,成果喜人:

  • 公共组件库从 3 套减为 1 套
  • 新员工接入 UI 组件时间从 2 天缩短到 2 小时
  • Bundle size 减少 18%(因为去掉了重复的 UI 逻辑)
  • 最重要的是——再也不用跟产品经理解释“为什么这个按钮在 A 页面圆角、B 页面方角”了

上周团建,测试组长敬我一杯:“你们那个 tooltip 终于不闪退了!” 我笑笑,心里清楚:不是我多牛,是选对了技术路径。

写在最后:代码人生的另一种可能

很多人觉得 Web Components 是“过时技术”,是“React/Vue 的替代品”。但我觉得,它更像是前端生态的胶水——不争不抢,默默连接一切。

在我们这种节奏慢、系统杂、人员流动低的国企,追求最新框架反而容易翻车。而 Web Components 这种“稳如老狗”的方案,既能满足交互需求(比如我最爱的微交互动画),又不会给运维添堵,简直是天选之子。

如果你也在维护“祖传代码”,或者被多框架折磨得睡不着觉,不妨试试 Web Components。不用全盘重构,就从一个按钮、一个 loading 开始。你会发现,原生的力量,往往最持久

对了,教程?网上一堆,但我建议直接看 MDN。别信那些“三天精通”的速成课——真正的代码人生,从来都是边踩坑边成长。

(写完这篇,已经凌晨一点。关电脑,回家。明天还要早起挤地铁呢。)

评论 0

最热最新
暂无评论
哈希表少年Lv.1
0
影响力
0
文章
0
粉丝