【React】DOM操作について（CreatePortal・Ref）

# ポータルとは

ReactにおけるDomツリーを操作するのに使用する機能。
対象の子要素を現在の直接的な親要素から、別のDOM要素にマウントすることができる。

createPortalは、そのポータルを作成するためのメソッド。

# 具体例
```react
import { useState } from "react";
import { createPortal } from "react-dom";

const ModalPortal = ({ children }) => {
  const containerStart = document.querySelector('.container.start');
  return createPortal(children, containerStart);
}


const Example = () => {
  const [modalOpen, setModalOpen] = useState(false);
  return (
    <div>
      <div className="container start"></div>

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

export default Example;
```

**コンポーネント**
```react
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;

```

# ポータルの注意点

## バブリング

子要素でイベント（クリックイベントなど）が発生した場合、その子要素に直接関係する`親要素`に伝播する。

このとき、親要素にも要素にハンドラが登録されている場合には、親要素のハンドラが実行される。

これはDOMが移動するポータルでも同様の現象が発生する。
```react
import { useState } from "react";
import { createPortal } from "react-dom";

const ModalPortal = ({ children }) => {
  const containerStart = document.querySelector('.container.start');
  return createPortal(children, containerStart);
}


const Example = () => {
  const [modalOpen, setModalOpen] = useState(false);
  return (
    <div onClick={ () => console.log("バブリング") }>
      <div className="container start"></div>

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

export default Example;
```
モーダルを表示すると、consoleに”バブリング”が出力される。

![スクリーンショット 2022-12-24 15.42.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/0ed86561-6f38-1044-6cab-81927175b037.png)

# Ref

React Hooksの１つ。
`DOMの参照`と`データの保持`の２つの機能を持っているフック。

useRefを使用して作成されるオブジェクトは`refオブジェクト`と呼び、`current`プロパティを持つ。このcurrentプロパティに値が保持される。
参照する際には、`変数.current`で呼び出し、呼び出しや書き換えを行う。

**例**
`currentプロパティ`に値を保持するには、保持したいDOMに対し`ref属性`を設定し、ref属性にrefオブジェクトを代入することでcurrentプロパティに保持される。
```react
import { useState, useRef } from "react";

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

  return (
    <div>
      <h3>ユースケース1</h3>
      {// ref属性にrefオブジェクトを渡している。}
      <input type="text" ref={inputRef} value={value} onChange={(e) => setValue(e.target.value)} />
      <button onClick={ () => inputRef.current.focus() }>
        インプット要素をフォーカスする
      </button>
    </div>
  );
};

const Example = () => {
  return (
    <>
      <Case1 />
    </>
  );
};

export default Example;
```

useRefで保持された値は、<font color="red">更新されても再描画が行われない</font>のが最大の特徴。

**Portalとの違い**
・Portalは特定の子要素を別の親要素に依存関係を変更する。

・refはDOMに直接にアクセスし、アクションを反映させる。

# forwardRef

他のコンポーネントのDOMにアクセスすることができる。

通常のuseRefで作成するrefオブジェクトはpropsでコンポーネントに渡すことができない。
```react
import { useRef } from "react";

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

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

export default Example;

```
![スクリーンショット 2022-12-24 18.23.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/ae8a3707-ed19-e2c0-64c1-fc5df1c707dd.png)

このやり方はReactでは推奨されていない。

## 解決方法①

**refというprops名を使わず、別のprops名で指定して渡す。**
```react
import { useRef } from "react";

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

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

export default Example;

```

`ref`はReact上では特別な単語なので、props名として`ref`を使用することが先ほどのエラーに繋がっている。
よって別の名前を使用することで解決が可能。

## 解決方法②
**forwardRef関数を使用する。**
```react
import { useRef, forwardRef } from "react";

/**
 * 第一引数にはprops、
 * 第二引数にrefオブジェクトを受け取るようにする。
 *
 * 通常の関数コンポーネントでは、第二引数を利用することはないが、
 * forwardRefは例外で第二引数を利用する。
 */
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>
    </>
  );
};

export default Example;

```
forwardRef関数を使用することで、`ref`でも渡すことが可能になる。
refの受け渡しについては、`親　→ 子`の一方通行であり、親から子のDOMを操作することができる。


①と②はどちらを使用しても良い。
<font color="red">しかし、この親から子にrefを渡すという行為は、コンポーネント間の依存関係を強めてしまう行為であるため、使用には注意が必要である。</font>

# useImperativeHandle

先ほどのforwardRefで、`親コンポーネントから`Inputコンポーネント`に対してrefオブジェクトを渡し、親から　Inputコンポーネントを操作できるようにした。

これは逆にいうと、Inputコンポーネントを親コンポーネントから自在に操作できてしまうというデメリットがある。
作成した作成者にとって意図しない操作も自在にできてしまうのはあまり良いこととは言えないので、操作を限定することが必要。

そこで使われるのが、`useImperativeHandle`である。

### 使い方
```react
import { useRef, forwardRef, useImperativeHandle } from "react";

const Input = forwardRef((props, ref) => {

  const inputRef = useRef();

  useImperativeHandle(ref, () => ({
    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>
    </>
  );
};

export default Example;

```

### ポイント①
・第一引数　→ 親から渡されたrefオブジェクトを受け取る。

・第二引数　→ 実行したいメソッドを含むオブジェクトを返す、関数を定義する。

```react
const Input = forwardRef((props, ref) => {
         .
         .
         .
  useImperativeHandle(ref, () => ({
    myFocus() {
      inputRef.current.focus();
    }
  }))

});

const Example = () => {
  const ref = useRef();
         .
         .
         .
  return <Input ref={ref} />;
}
```
### ポイント②
子コンポーネントのref属性には、<font color="red">親から渡されたrefオブジェクトとは別のrefオブジェクトを代入する。</font>

```react

const Input = forwardRef((props, ref) => {

  {// useRefオブジェクトを新規作成。}
  const inputRef = useRef();

  useImperativeHandle(ref, () => ({
    myFocus() {
      inputRef.current.focus();
    }
  }))

  {// 子で新規作成したコンポーネントをrefに代入。}
  return <input type="text" ref={inputRef} />;
});
```

#### なぜそうするのか

子コンポーネントの`useImperativeHandle`で定義したメソッドを親から実行するという形を取るため。
refを直接代入したら、結局親から自在に操作できることになってしまう。よって、

**①子コンポーネントで一度新規でrefオブジェクトを作成する。**

**②`useImperativeHandle`の第二引数で、「新規refオブジェクトのcurrentプロパティに対してイベントを実行する」というメソッドを定義する。**

**③`useImperativeHandle`の第二引数で定義したメソッドは、第一引数で設定した親のrefオブジェクトからアクセスができるので、そのメソッドを実行。**

**④親から子コンポーネントで定義した新規refオブジェクトに直接アクセスする方法はなく、子コンポーネントのrefにはその新規refオブジェクトを代入しているため、親からの操作を限定できている。**


という流れになる。

# 最後に
途中にも記載をしたように、親子のコンポーネント間でpropsでrefを受け渡しする行為は、

<font color="red">**①親子間の依存関係を強めてしまう。**</font>

<font color="red">**②処理が行ったり来たりするためコードが煩雑になる ＋ 可読性が悪くなる。**</font>

ということが起きるため、他に解決策がある場合はそちらを採用することが推奨されている。