Try   HackMD

React Portal, 在 root 以外掛載元件

tags: React Portal ReactDom

React 提供了 Portal 方法,讓 children 可以 render 到 parent component DOM 樹以外的 DOM 節點。

像是以下範例

...
return  (
    <React.Fragment>
        <MyModal />
        <MyForm />
    </React.Fragment>
)
...

大至會產出以下 Real DOM

<section>
    <h2>Some Other content</h2>
    <div class='my-modal'>
        <h2>My Modal Title</h2>
    </div>
    <form>
        <label>Username</label>
        <input type='text' />
    </form>
</section>

但當 Modal 元件位置越來越深時,就會產生一些問題,在 HTML 語意上 Modal 作用使覆蓋整個頁面,嵌套在在某個元件中是較不理想的!

要如何改變掛載的節點呢?

到 public/index.html 找到 id 為 root 的 DOM,並在前方新增 modal-root & backdrop-root id

<body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="backdrop-root"></div>
    <div id="modal-root"></div>
    <div id="root"></div>
    ...
</body>

到 ErrorModal 元件中有 backdrop 以及 modal 兩個平行的 div

const ErrorModal = (props) => {
  return (
    <>
      <div className={classes.backdrop} />
      <Card className={classes.modal}>
        ....
      </Card>
    </>
  );
};

export default ErrorModal;

backdrop 以及 modal 分開定義變數

並且使用 ReactDOM.createPortal() 掛載至指定 DOM

// ReactDOM
import ReactDOM from "react-dom";

// Backdrop
const Backdrop = (props) => {
  return <div className={classes.backdrop} />;
};

// ModalOverlay
const ModalOverlay = (props) => {
  return (
    <Card className={classes.modal}>
      ...
    </Card>
  );
};

// 掛載節點
const ErrorModal = (props) => {
  return (
    <>
      {ReactDOM.createPortal(
        <Backdrop />,
        document.getElementById("backdrop-root")
      )}
      {ReactDOM.createPortal(
        <ModalOverlay />,
        document.getElementById("modal-root")
      )}
    </>
  );
};

export default ErrorModal;

到 chrome 觀察 可以發現剛剛新增的兩個節點裡面都有指定的元件在內了!

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

觀察入口 index.js,使用 ReactDOM.render 訪法將 JSX 渲染至 root DOM 上,而 React.createPortal 也是使用差不多的邏輯

import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));

差別是我們不是將 ErrorModal 元件另外在另一個 DOM 上做渲染,而是改變已渲染完成的元件掛載的節點。