先描述下
- Web Components 允许开发者创建可重用的自定义元素,它们可以一起使用来创建封装功能的自定义元素,并可以像浏览器原生的元素一样在任何地方重用,而不必担心样式和 DOM 的冲突问题
- Shadow DOM(影子 DOM):一组 JavaScript API 用于将「影子 DOM 树」附加到元素上,与主文档 DOM 树隔离,并能控制其关联的功能。通过这种方式,可以保持元素的私有,并能不用担心「样式」与文档的其他部分发生冲突。
问题
- 隔离在 Shadow Root 中的元素上的事件无法被触发,当在 Shadow DOM 外部捕获时浏览器会对事件进行「重定向」,也就是说在 Shadow DOM 中发生的事件在外部捕获时将会使用 host 元素作为事件源。这将让 React 在处理合成事件时,不认为 ShadowDOM 中元素基于 JSX 语法绑定的事件被触发。
- 样式隔离问题,意味着全局样式必须在每个前端组件挂载最近的 Shadow Root 下手动注入进 ?<style>? 标签,否则的样式会被隔离(所有样式都必须在 Shawdow Root 下否则会被隔离);
事件捕获解决方案
- ReactDOM.createPortal 有一个特性是「通过 createPortal 渲染的 DOM,事件可以从 Portal 的入口端冒泡上来」,通过这个关键特性,没有父子关系的 DOM ,合成事件能冒泡过来,那通过 createPortal 渲染到 Shadow DOM 中的元素的事件也能正常触发
import React from "react";import ReactDOM from "react-dom";
export function ShadowContent({ root, children }) {
return ReactDOM.createPortal(children, root);}
export class ShadowView extends React.Component {
state = { root: null };
setRoot = eleemnt => {
const root = eleemnt.attachShadow({ mode: "open" });
this.setState({ root });
};
render() {
const { children } = this.props;
const { root } = this.state;
return <div ref={this.setRoot}>
{root && <ShadowContent root={root} >
{children}
</ShadowContent>}
</div>;
}}
export class App extends React.Component {
state = { message: '...' };
onBtnClick = () => {
this.setState({ message: 'haha' });
}
render() {
const { message } = this.state;
return <ShadowView>
<div>{message}</div>
<button onClick={this.onBtnClick}>单击我</button>
</ShadowView>
}}
ReactDOM.render(<App />, document.getElementById("root"));
// https://github.com/styled-components/styled-components/pull/1491
<StyleSheetManager target={target.shadowRoot}>
<React.Fragment>
{/* your children here */}
</React.Fragment>
</StyleSheetManager>
使用 ReactDOM.createPortal 我还解决了这个问题:?? 如何优雅地解决多个 React、Vue App 之间的状态共享?
——————————————————————————————————————————————————
如果能够对你有帮助就请「点赞」「采纳」吧,感谢 ??
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…