一、描述

一般使用 React 的组件都是挂到父组件的 this.props.children 上面,总是被最近的父组件所捕获,最终到 React 根组件上。

而 Protals 则提供了一种将组件直接挂载到直接父组件 DOM 层次之外的一类方式。

react-dom 提供的具体方法是 ReactDOM.createPortals(child, container),这个方法需要两个参数,第一个参数是需要挂载的组件实例,而第二个参数则是要挂载到的DOM节点。一般来说第一个参数可能传递的是需要挂载的 this.props.children

二、之前的 Modal 组件

以一个 Modal 弹窗举例(官网也是用的这个例子)。

正常来说,我要创建一个 Modal 组件,可以按照下面这么写:

const styles = {
  modal: {
    position: 'fixed',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    backgroundColor: 'rgba(0,0,0,0.3)',
    display: 'flex',
    justifyContent: 'center',
    alignItems: 'center'
  }
}

class Modal extends Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <div style={styles.modal}>
        {this.props.children}
      </div>
    );
  }
}

而使用的时候直接引入并且写子组件即可:

<div className="App">
        <Modal>
          <h1>Modal</h1>
        </Modal>
 </div>

最终的效果如下:

a.png

Modal 的组件实例这种实现方式也可以,这里的突破方式其实就是直接将 Modal 组件位置进行 fixed 定位。

但是更多时间如果父组件容器存在 overflow: hidden 或者存在 z-index 的时候,需要在视觉上突破父组件的容器,使用 createPortal 更好一些,因为他可以随便将组件挂到任何的 DOM 下。

三、 createProtal 改造 Modal 组件:

1、html 预留容器

在 html 中除了 div#root 之外,给 Modal 预留了一个新的 div#modal-root,:

const appRoot = document.getElementById('root');
const modalRoot = document.getElementById('modal-root');

为了突出 div#root 容器宽度高度很小,并且 overflow:hidden,给 div#root 一个样式:

#root {
  height: 10em;
  width: 10em;
  background: lightblue;
  overflow: hidden;
}

2、改造 Modal 容器

新的 Modal 容器组件内容如下:

class ModalContainer extends Component {
  constructor(props) {
    super(props);
    this.el = document.createElement('div');
  }
  componentDidMount() {
    modalRoot.appendChild(this.el);
  }
  componentWillUnmount() {
    modalRoot.removeChild(this.el);
  }
  render() {
    return ReactDOM.createPortal(
      this.props.children,
      this.el
    );
  }
}

因为需要保证 ModalContainer 始终保持在最上层,不会被其他 z-index 覆盖掉,因此需要给 div#modal-root 一个样式:

#modal-root {
  position: relative;
  z-index: 999;
}

3、Modal 组件的内容

上面只是 Modal 的容器组件,最主要的作用就是将 this.props.children 挂载到组件外部去(div#modal-root

需要创建 Modal 需要显示的内容组件:

#modal-root {
  position: relative;
  z-index: 999;
}

4、使用 Modal 容器组件和内容组件

将 ModalContent 挂载到 ModalContainer 中即可:

class App2 extends Component {
  state = {
    name: 'clickme'
  }
  componentDidMount(){
    // console.log(findDOMNode(ref.current))
  }
  clickHandle = () => {
    this.setState({
      name: 'clickme' + Date.now()
    });
  }
render() {
  return ( 
      <div className="App">
        <ModalContainer>
          <ModalContent />
        </ModalContainer>
      </div>
  );
}
}

5、效果

可以发现,即使主组件是 overflow:hidden 的,但是 modal 能够正常出现,同时,因为 div#modal-rootz-index: 999,因此会置于最上层。

GIF.gif

可以发现,在父组件里捕获一个来自 Portal 的事件冒泡很简单,在开发时不需要完全依赖于 Portal ,对于时间的捕获更加的灵活。