React 黑魔法之 Portal + SyntheticEvent + iframe

quote

One of the things I loved about working on 'Portal' was that we'd get emails from people saying, 'I love to play first-person shooters but my girlfriend won't play them with me. But I got her to play 'Portal' and she had a blast.'

Kim Swift,

在实现划词扩展的时候,查词面板等模块需要植入到源网页,为了更方便地隔离样式污染,植入的模块均使用了 <iframe> 包装。在前一个 Vue 实现的版本 Saladict 5 中,有几个不太舒服的小小小小地方:

  1. <iframe> 内外事件不通。
  2. <iframe> 内外环境不一样,变量不能共用,需要 postMessage 交流。
  3. Vue 组件必须要有一个根元素,且 v-if 隐藏之后元素原地还是会留下注释标记。在浏览器审查元素时看起来不太干净。

当然这个不影响呈现效果,所以当时也不怎么纠结了。然而后来在逛 React 文档的时候,意外发现了 Portal 这个神器。

Portal 精妙的地方在于从 React 的角度(也是代码作者的角度),组件可以保持原来的结构,但实际可以渲染到任意地方(甚至是其它窗口!)。

更令人拍案叫绝的是, SyntheticEvent 也是抽象到 React 层的,所以组件事件捕获冒泡全正常使用。

对于 <iframe> 需要做些小改变,有人发现,只需要把 <body> 组件上所有事件 null 掉即可。

这样就方便地解决了前两个小痛点了!

最后一点的解决方式是在组件不显示的时候将 Portal 的载体元素 remove 掉,显示时再 appendChild 回去。甚至配合动画也非常简单,在动画结束时 remove 即可。

最后如果对实现感兴趣的话可以参考:

值得一提的是,也有人吸取灵感实现了 Vue 版的 Portal。然而这个功能没有得到官方青睐,只是社区实现。由于没有内核权限,相比于 React Portal 这仅是部分实现。