跳至主要內容

Portals

XinYang's Blog大约 2 分钟React

Portal 的作用是可以将子节点渲染到父组件以外的地方。

Portal 用法示例:

import { createPortal } from 'react-dom';

...
return createPortal(child,container);
...

Portal 接受的第一个参数是任何可渲染的React子元素,第二个元素是一个DOM元素。

一个 portal 的典型用例是当父组件有 overflow: hiddenz-index 样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框:

查看不使用Portal示例代码
父组件
import ChildHook from "./components/Child-hook";

const App = () => {
  return (
    <>
      <p>父组件hook</p>
      <ChildHook>
        <p>向子组件传递一个组件</p>
      </ChildHook>
    </>
  );
};

export default App;

查看使用Portal示例代码
JS
import React, { useState, useEffect } from "react";
import { createPortal } from "react-dom";
import "./App.css";

// hook
const Modal = props => {
  const model_el = document.createElement("div");
  const modal_container = document.querySelector(".modal-container");
  useEffect(() => {
    modal_container.appendChild(model_el);
    return function cleanUpComponent() {
      modal_container.removeChild(model_el);
    };
  });
  //
  return createPortal(props.children, model_el);
};

// class
// class Modal extends React.Component {
//   constructor(props) {
//     super(props);
//     this.modal_container = document.querySelector(".modal-container");
//     this.el = document.createElement("div");
//   }
//   componentDidMount() {
//     this.modal_container.appendChild(this.el);
//   }
//   componentWillUnmount() {
//     this.modal_container.removeChild(this.el);
//   }
//   render() {
//     return createPortal(this.props.children, this.el);
//   }
// }

const App = () => {
  const [isShow, setIsShow] = useState(false);
  let modal = isShow ? (
    <Modal>
      <div className="modal">
        <div>With a portal, we can render content into a different part of the DOM, as if it were any other React child.</div>
        This is being rendered inside the #modal-container div.
        <button onClick={() => setIsShow(false)}>Hide Modal</button>
      </div>
    </Modal>
  ) : null;
  return (
    <>
      <div className="modal-control">
        This div has overflow: hidden.
        <button onClick={() => setIsShow(true)}>Show Modal</button>
        {modal}
      </div>
      <div className="modal-container"></div>
    </>
  );
};

export default App;

即使portal可以被放置在DOM树任何地方,哪怕挂载的节点不是由React进行托管,例如,body下有两个节点,分别是id为app-rootmodal-root,React挂载的是app-root,但是可以通过portal将模态框渲染到modal-root上。但是由于portal存在React树中,与DOM树中的位置无关,那么包括context、事件绑定一样还是会起作用的,同时无论是否你是有portal进行渲染的。举例以下DOM结构:

<html>
  <body>
    <div id="app-root"></div>
    <div id="modal-root"></div>
  </body>
</html>

一个Protal内部触发的事件会一直冒泡至包含React树的祖先。