Web Component 踩坑指南:Shadow DOM 里的样式与事件

38 阅读2分钟

背景

最近在开发 AutoForm SDK 时,为了实现"一次开发,到处运行",我们选择了 Web Component 技术方案。

本以为是银弹,结果踩了一路的坑。今天把这些血泪经验总结出来,希望能帮大家少走弯路。

坑一:样式隔离太彻底了

Shadow DOM 的初衷是隔离样式,防止污染。但有时候我们让外部样式影响内部,比如让用户自定义字体。

现象:宿主页面设置了 body { font-family: 'Roboto' },但 SDK 里的字体还是浏览器的默认字体(Times New Roman)。

解决方案

  1. CSS 变量穿透:这是最推荐的方式。
    /* SDK 内部 */
    :host {
      font-family: var(--af-font-family, sans-serif);
    }
    
  2. ::part 伪元素:允许外部选择 Shadow DOM 内部标记了 part 的元素。
    <!-- SDK 内部 -->
    <button part="submit-btn">提交</button>
    
    /* 外部 CSS */
    autoform-widget::part(submit-btn) {
      background: red;
    }
    

坑二:React 的合成事件失效

这是最经典的一个坑。React 16 及以前版本,把事件代理到了 document 上。

现象:在 React 项目中引入我们的 SDK,点击 SDK 里的按钮,React 的 onClick 根本不触发。

原因:事件从 Shadow DOM 冒泡出来时,target 被重定向到了 Host 元素。React 发现 target 变了,或者事件在 Shadow Boundary 被截断了,就无法正确分发。

解决方案

  1. 升级 React 17+:React 17 改为将事件代理到 Root 节点,缓解了部分问题。
  2. 手动派发事件:在 SDK 内部,手动 dispatch 一个 composed: true 的自定义事件。

坑三:弹窗被 overflow: hidden 截断

如果你的 Web Component 放在一个 overflow: hidden 的容器里,而你又想弹出一个超出容器的 Modal 或 Tooltip,你会发现它被截断了。

原因:Shadow DOM 并没有创建一个新的层叠上下文(Stacking Context)来逃离父级的 overflow 限制。

解决方案: 使用 React Portal 或类似的机制(Svelte 的 Portal),将 Modal 渲染到 document.body 下,而不是 Shadow DOM 内部。

但这样又会带来新问题:渲染到 body 下的元素失去了 Shadow DOM 的样式隔离保护!

终极方案: 创建一个新的 Web Component 专门用来放 Modal,并将其挂载到 body 下。

总结

Web Component 虽然标准很美好,但在实际工程中,特别是与现有框架(React/Vue)混用时,还是有很多细节需要注意。

希望这些经验能帮到正在折腾 Web Component 的你。

👉 官网地址:51bpms.com