React Portal
Portal 是從 React 16 開始提供的功能,用來將元件渲染到父元件之外的 DOM 節點上。可以將 Portal 想像成,你 render 一個元件時,其實是改變頁面上某個地方的 DOM 結構。
你可能會想,那 Portal 是用在什麼情況?通常的 React 元件,你只能渲染在父元件下面,但如果你要寫一個 modal 對話框,或說父元件寫死了 overflow: hidden 或你遇到 z-index 的問題呢?這就是使用 Portal 的時候了!
你可以在 render() 中使用 Portal:
ReactDOM.createPortal(child, container)
其中第一個參數 child 是你要渲染的 React 元素,第二個參數 container 則是要被掛載到哪一個 DOM 元素上。
例如頁面中的 HTML 結構如果是:
<html>
<body>
<div id="app-root"></div>
<div id="modal-root"></div>
</body>
</html>
你可以這樣子寫一個 modal:
// DOM 中的兩個兄弟元素 app-root 和 modal-root
const appRoot = document.getElementById('app-root');
const modalRoot = document.getElementById('modal-root');
// 一個 modal 元件
class Modal extends React.Component {
constructor(props) {
super(props);
// 因為我們想要每個 modal 都是獨立分開的
// 所以每個 modal 都建立一個自己的 DOM container
this.el = document.createElement('div');
}
componentDidMount() {
// 元件 mount 時,將 container 放到 #modal-root 元素中
modalRoot.appendChild(this.el);
}
componentWillUnmount() {
// 元件 unmount 時,移除 container
modalRoot.removeChild(this.el);
}
render() {
// 運用 Portal 功能,將 React 元素 render 到指定的 DOM element
return ReactDOM.createPortal(
this.props.children,
// 第二個參數指定元件要掛載到這個 DOM element
this.el,
);
}
}
// 接著,像一般元件的使用,我們可以在任意元件中使用 Modal 元件
class App extends React.Component {
constructor(props) {
super(props);
this.state = {showModal: false};
this.handleShow = this.handleShow.bind(this);
this.handleHide = this.handleHide.bind(this);
}
handleShow() {
this.setState({showModal: true});
}
handleHide() {
this.setState({showModal: false});
}
render() {
// 點按鈕來出現 modal
const modal = this.state.showModal ? (
<Modal>
<div className="modal">
我是被渲染到 #modal-container 的 modal
<button onClick={this.handleHide}>關掉 modal</button>
</div>
</Modal>
) : null;
return (
<div className="app">
<button onClick={this.handleShow}>打開 modal</button>
{modal}
</div>
);
}
}
ReactDOM.render(<App />, appRoot);
Portal 的事件冒泡 (Event Bubbling)
雖然 Portal 可能會被掛載到 DOM 中任何的地方,但使用上和一般的 React 元件都是一樣的,React 底層都幫你處理好了!
這包含事件的處理,像是上面的例子中的 HTML 結構:
<html>
<body>
<div id="app-root"></div>
<div id="modal-root"></div>
</body>
</html>
在父元件 #app-root 裡面,是能夠捕抓到 #modal-root 冒泡上來的事件喔!(在 DOM 結構中,因為 #modal-root 是 #app-root 的兄弟節點,不是 #app-root 的子節點,理論上 #app-root 是收不到 #modal-root 的事件的)