在线演示https://myp00yr1xx.codesandbox.io/react-portal
在线代码https://codesandbox.io/embed/myp00yr1xx?fontsize=14
不知道大家想过这样一个问题没有,我们创建了一个子组件后,要把它放到指定的dom元素下面,该怎么办?
ReactDom提供了这样一个api
// child可以是ReactElement, Arrays, fragments, Portals, String, numbers,
// Booleans, null, ...
// container是dom元素
ReactDOM.createPortal(child, container)
下面代码是创建一个Foo组件(表现为200*200的div),放到body的中央位置。
import React from "react";
import ReactDom from "react-dom";
export default class extends React.Component {
div = document.createElement("div");
componentWillUnmount() {
document.body.removeChild(this.div);
}
componentDidMount() {
document.body.appendChild(this.div);
}
render() {
return ReactDom.createPortal(<Foo />, this.div);
}
}
const styles = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 200,
height: 200,
zIndex: 100,
background: "rgba(222,222,222,0.4)",
boxShadow: "5px 5px 5px 5px gray"
};
const Foo = () => {
return <div style={styles}>Portals的使用</div>;
};
Portals的事件传递
我们改造下上面的代码:
import React from "react";
import ReactDom from "react-dom";
class App extends React.Component {
div = document.createElement("div");
componentWillUnmount() {
document.body.removeChild(this.div);
}
componentDidMount() {
document.body.appendChild(this.div);
}
render() {
return ReactDom.createPortal(<Foo />, this.div);
}
}
const styles = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 200,
height: 200,
zIndex: 100,
background: "rgba(222,222,222,0.4)",
boxShadow: "5px 5px 5px 5px gray"
};
const Foo = () => {
return (
<div onClick={() => console.info("触发点击事件")} style={styles}>
Portals的使用
</div>
);
};
export default () => (
<div
style={{ border: "1px solid red" }}
onClick={() => console.info("点击事件冒泡到其React的虚拟DOM父节点")}
>
<p>React虚拟DOM父节点</p>
<App />
</div>
);
虽然我们通过Dom操作,把它移到了document.body下,与React 的虚拟Dom结构不一致,但是React 的虚拟Dom结构并未发生变化,父子关系仍然存在。这就导致,事件的传递依然有效,在Foo组件触发的click事件,依然会传递到App组件。