这里我们引入Portal组件,这是一个经典的实现,最初的实现来源于React Bootstrap组件中的Overlay Mixin,后来应用越来越广泛。我们截取的关键代码如下:
import React from 'react';
import ReactDOM, {findDOMNode} from 'react-dom';
// import CSSPropertyOperations from './react/lib/CSSPropertyOperation'
export default class Portal extends React.Component{
constructor (props) {
super(props);
//...
};
openPortal(props = this.props){
this.setState({active: true});
this.renderPortal(props);
this.props.onOpen(this.node);
};
closePortal(isUnmounted = false){
const resetPortalState = () => {
if (this.node) {
ReactDOM.unmountComponentAtNode(this.node);
document.body.removeChild(this.node);
}
this.portal = null;
this.node = null;
if (isUnmounted !== true) {
this.setState({active: false});
}
};
if (this.state.active) {
if (this.props.beforeClose) {
this.props.beforeClose(this.node, resetPortalState);
} else {
resetPortalState();
}
this.props.onClose();
}
};
renderPortal(props){
if (!this.node) {
this.node = document.createElement('div');
//在节点增加到DOM之前,执行CSS防止无效的重绘
this.applyClassNameAndStyle(props);
document.body.appendChild(this.node);
} else {
//当新的props传下来的时候,更新CSS
this.applyClassNameAndStyle(props);
}
let children = props.children;
if (typeof props.children.type === 'function') {
children = React.cloneElement(props.children, {closePortal: this.closePortal});
}
this.portal = ReactDOM.unstable_renderSubtreeIntoContainer(
this,
children,
this.node,
this.props.onUpdate
)
};
render(){
if (this.props.openByClickOn) {
return React.cloneElement(this.props.openByClickOn, {onClick: this.handleWrapperClick})
}
return null;
}
}
从Portal组价你可以看出,我们实现了一个“壳”,其中包括触发事件、渲染的位置以及关闭的方法,但它并不关心子组件的内容。当我们使用它的时候,可以这么写:
<Portal ref="myPortal">
<Modal title="My modal">some content</Modal>
</Portal>