0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

React完全攻略ガイド ~ReactでDOM操作を行う~

Last updated at Posted at 2025-10-08

参考教材

ReactでDOM操作を行う

モーダルの作り方

ポータルの子要素を、直接の親要素ではなく別のDOM要素にマウントすることができる

/* POINT createPortalの使い方
第一引数: React の子要素としてレンダー可能なもの (要素、文字列、フラグメント、コンポーネントなど)
第二引数: レンダー先のDOM要素
*/

/* POINT createPortalはどんなときに使うか?
子要素は親要素のスタイルによって表示に制限を受ける場合があります。
(overflow: hidden 、 z-index 、 width など・・・ )
それらの制限なく、子要素が親要素を「飛び出して」表示する必要があるときにcreatePortalを使うのが有効です。
モーダル、ポップアップ、トーストは使用の代表例です。
*/
const ModalPortal = ({ children }) => {
  const target = document.querySelector(".container.start")
  return createPortal(children, target)
};

const Example = () => {
  const [modalOpen, setModalOpen] = useState(false);
  return (
    <div>
      {/* ここの空divの中にモーダルが表示される */}
      <div className="container start"></div>

      <button
        type="button"
        onClick={() => setModalOpen(true)}
        disabled={modalOpen}
      >
        モーダルを表示する
      </button>
      {/* modalOpenがTrueの時、モーダルが表示される(初期値はFalse) */}
      {modalOpen && (
        <ModalPortal>
          <Modal handleCloseClick={() => setModalOpen(false)} />
        </ModalPortal>
      )}
    </div>
  );
};

2025-10-08 11.09の画像.jpeg

Portalを使う注意点

Bubblingとは

イベントが子要素から親要素に伝播すること

refでDOMを直接操作しよう

useRef

Reactからは直接DOMを操作しないが、操作によってはDOMに直接命令を与えたい場合がある。そのときはuseRefを使う

import { useState, useRef } from "react";

const Case1 = () => {
  const [value, setValue] = useState("");
  const inputRef = useRef();

  return (
    <div>
      <h3>ユースケース1</h3>
      {/* inputのJSXのコードから作成されるDOM要素の参照をinputRefが保持する */}
      <input type="text" ref={inputRef} value={value} onChange={(e) => setValue(e.target.value)} />
      <button onClick={() => console.log(inputRef)}>
        インプット要素をフォーカスする
      </button>
    </div>
  );
};

2025-10-08 11.30の画像.jpeg

( ▶️current: input )の中にDOM要素が保持されている

<button onClick={() => inputRef.current.focus()}>

focus(DOMのメソッド)をDOM要素の入っている current に続けてメソッドを読む
→ 入力欄にフォーカスが移る

2025-10-08 11.37の画像.jpeg

refで動画プレイヤーを作成してみよう

const Case2 = () => {
  //動画が再生中かどうかをStateで定義
  const [playing, setPlaying] = useState(false);

  //videoのDOMを取得
  const videoRef = useRef();

  return (
    <div>
      <video style={{ maxWidth: "100%" }} ref={videoRef}>
        <source src="./sample.mp4"></source>
      </video>

      <button onClick={() => {

        if (playing) {
          //pause()...動画の再生が止まる
          videoRef.current.pause();
        } else {
          //play()...動画が再生される
          videoRef.current.play();
        }

        //真偽値を逆にする(NOT演算子)
        setPlaying(prev => !prev)
      }}>
        {playing ? "Stop" : "play"}
      </button>
    </div>
  )
}

2025-10-08 22.10の画像.jpeg

play を押したら動画が始まり、stop を押したら動画が止まる

useRefとは?

再レンダリングを発生させずに値を保持する方法

・useRefは ”refオプジェクト" を返す

const videoRef = useRef();

・currentプロパティに値が設定される
・ref.currentで値にアクセスできる。値の読み書き可能

2025-10-08 22.24の画像.jpeg

refの特徴

①再レンダリングされても情報は保持される

②refの値を変更しても再レンダリングがトリガーされない

再レンダリングがされないと、関数コンポーネントの再実行もないので画面上の変更はない。

③最も一般的な利用法として、refオプジェクトをJSXのref属性に渡すとそのDOMにアクセスできる

<video style={{ maxWidth: "100%" }} ref={videoRef}>

他のコンポーネントのDOMにアクセスする方法

Refはpropsとしては渡せない

//NG
const input = ({ ref }) => {
  return <input type="text" ref={ref} />
};

const Example = () => {
  const ref = useRef();
  return (
    <>
      <input ref={ref}/>
      <button onClick={() => ref.current.focus()}>
        インプット要素をフォーカスする
      </button>
    </>
  );
};

解決策① refの名前を使わない

refは特別な属性なので、refという名前を使わない

const Input = ({ customRef }) => {
  return <input type="text" ref={customRef} />
};

const Example = () => {
  const ref = useRef();
  return (
    <>
      <Input customRef={ref}/>
      <button onClick={() => ref.current.focus()}>
        インプット要素をフォーカスする
      </button>
    </>
  );
};

解決策② forwardRefを使う

//第一引数にprops、第二引数にrefを渡す
const Input = forwardRef((props, ref) => {
  return <input type="text" ref={ref} />
});

const Example = () => {
  const ref = useRef();
  return (
    <>
      <Input ref={ref}/>
      <button onClick={() => ref.current.focus()}>
        インプット要素をフォーカスする
      </button>
    </>
  );
};

個人開発ではrefに独自の値をつけて解決策①でもいいが、外部に公開するライブラリの作成や他の開発者用のコンポーネントではAPIを統一するために解決策②(forwardRef)を使おう

refへのアクセスを限定する

useImperativeHandle

forwardRefを使った実装をすると、ref.currentにDOMが入ってくるので、あらゆる操作が可能になる。そのためコンポーネントの開発者の意図しない使われ方を防止するために使う。refを使った操作を限定できる。

/* POINT forwardRef
子コンポーネント内の DOM に直接アクセスしたいときに使います。
refは、親から子コンポーネントへprops形式で渡して参照するということができないため、
参照したい場合は子コンポーネント内でfowardRefを使用する必要があります。
*/
const Input = forwardRef((props, ref) => {

  //親コンポーネントから渡ってきたrefと子コンポーネント内で使うrefを別のrefにする
  const inputRef = useRef();

  //子コンポーネントで使う
  //第一引数には親コンポーネントのrefを使う
  useImperativeHandle(ref, () => ({
    //focusメソッドに限定する
    myFocus() {
      inputRef.current.focus();
    }
  }))

return <input type="text" ref={inputRef} />;
});

const Example = () => {
  const ref = useRef();
  return (
    <>
      <Input ref={ref} />
      <button onClick={() => ref.current.myFocus()}>
        インプット要素をフォーカスする
      </button>
    </>
  );
};

() => ({}) は () => {return()} の省略


⚠️ forwardRefやuseImperativeHandleはあまり使わない方がいい

基本的にStateで解決できるものには使わない方がいい。子要素のDOMの扱う数が増えると、どこで何をやっているのか分からなくなる。Reactの開発では、親から子に渡すことはあるが、子から親に渡すのは非推奨。

その理由は、親から子に一貫してデータを渡せば一方向のデータの流れだけを追えばいいが、子から親にデータの流れが移るので、コードが煩雑になる。

もし動画の再生などで必要になったら、useImperativeHandleで操作方法を限定してあげた方がいい


0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?