【React】でDOMを操作するcreatePortalの使い方と使い所と注意点

React

ReactでDOMを操作するcreatePortalの使い方と使い所と注意点を紹介します。

ReactでDOMを操作するcreatePortalとは

createPortalというメソッドを使うことで、本来React要素のツリーで親子関係にない要素を指定することが可能になります。
createPortalは、子のDOM要素を、親子関係のないところに作成するときに使います。
マウント先のHTMLを変更できるということです。

createPortalの使い方

第一引数: React の子要素としてレンダー可能なもの (要素、文字列、フラグメント、コンポーネントなど)を指定します。
第二引数: レンダー先のDOM要素を指定します。

createPortalの使いどころ

子要素は親要素のスタイル(overflow、z-index、width、heightなど)によって表示に一定の制限を受けます。
その制限に関係なく、子要素を表示したいときにcreatePortalを使います。
機能として代表的なものは、モーダル、ポップアップ、トーストなどがあります。

createPortalの例

まずは、createPortalを使用していないバージョンが以下になります。

Sample.js

import { useState } from "react";
import { createPortal } from "react-dom";
import Modal from "./components/Modal";


const Sample = () => {
  const [modalOpen, setModalOpen] = useState(false);
  return (
    <div className="parent">
      <div className="modal__container"></div>

      <button
        type="button"
        onClick={() => setModalOpen(true)}
        disabled={modalOpen}
      >
        モーダルを表示
      </button>
      {modalOpen && <Modal handleCloseClick={() => setModalOpen(false)} />}
    </div>
  );
};

export default Sample;

Modal.js

import "./Modal.css";

const Modal = ({ handleCloseClick }) => {
  return (
    <div className="modal">
      <div className="modal__content">
        <p>モーダル</p>
        <button type="button" onClick={handleCloseClick}>
          閉じる
        </button>
      </div>
    </div>
  );
};

export default Modal;

このモーダルは、Sample.jsのparentクラスの下に表示されることになります。
実際は、modal__containerクラスの中に表示したいという場合、createPortalを使用する必要があります。

createPortalを使ったバージョン

import { useState } from "react";
import { createPortal } from "react-dom";
import Modal from "./components/Modal";

const ModalPortal = ({ children }) => {
  const target = document.querySelector(".modal__container");
  return createPortal(children, target);
};
const Sample = () => {
  const [modalOpen, setModalOpen] = useState(false);
  return (
    <div className="parent">
      <div className="modal__container"></div>

      <button
        type="button"
        onClick={() => setModalOpen(true)}
        disabled={modalOpen}
      >
        モーダルを表示
      </button>

      {modalOpen && (
        <ModalPortal>
          <Modal handleCloseClick={() => setModalOpen(false)} />
        </ModalPortal>
      )}
    </div>
  );
};

export default Sample;

まず、react-domからcreatePortalをimportします。

import { createPortal } from "react-dom";

ポータルとなるコンポーネントを作成します。

const ModalPortal = ({ children }) => {
  const target = document.querySelector(".modal__container");
  return createPortal(children, target);
};

上にも記述していますが、第一引数にはReactの子要素としてレンダー可能なもの(要素、文字列、フラグメント、コンポーネントなど)を指定します。
第二引数には、レンダー先のDOM要素を指定します。上の場合だと第一引数は、Modalコンポーネントで、第二引数がmodal__containerクラスとなります。

        <ModalPortal>
          <Modal handleCloseClick={() => setModalOpen(false)} />
        </ModalPortal>

そして、最後にコンポーネントのようにして使用することができます。
これで指定したコンポーネントのマウント先のHTMLの任意の場所に変更することができます。

createPortalの注意点

createPortalでポータルを使うとき、バブリングには注意する必要があります。
バブリングとは、子要素のクリックイベントが親要素に伝搬する(子のクリックイベントが発生したら親のイベントの発火する)現象をいいます。
詳しくは、JavaScriptのイベントの伝播について【キャプチャリングとバブリング】を参照してみてください。
ポータルを使用すると、ポータル使用前の子要素の位置でクリックイベントが発生した場合のバブリングと同じ挙動になるので、注意が必要です。
上のケースでいうと、parentクラスのハンドラーにイベントが登録されていた場合、それも発火します。
ややこしい点としては、本来DOMツリー上でバブリングが発生するのに対し、ReactではReactツリー上でバブリングが発生します。
なので、検証ツールなどで実DOMをチェックしたときに挙動を理解できないときがありますが、上のReactツリーでバブリングが発生しているという点を思い出すようにしましょう。

React

Posted by devsakaso