お嬢様が教えるuseRef()

## 教材

ShinCodeさんの

【**完全保存版】React Hooksを完全に理解するHooksマスター講座【React18~19対応】**

https://www.udemy.com/course/react-hooks-complete-course/?couponCode=ST19MT60324

を元に、私が学んだ内容をまとめていきますわ。

## お嬢様的Hooks解説

[お嬢様が解説するuseState()](https://qiita.com/Yamamoto1012/items/c5e61169ffa2ec98fba0)

[お嬢様が解説するuseEffect()](https://qiita.com/Yamamoto1012/items/cebafee5b88e0ced06de)

[お嬢様が解説するuseRef()](https://qiita.com/Yamamoto1012/items/05c5bc8277e4e030e4eb)

## **useRef**
```tsx
const ref = useRef(initialValue)
```

`useRef`は、レンダー時に不要な値を参照するためのReactフックですの。`useRef`を使用することで、値を保持しつつもコンポーネントの再レンダリングを防ぐことができますの。

### **useRefの特性**

- **値の保持**: `useRef`は値を保持するために使用されますの。
- **再レンダリングしない**: `useState`と違い、`useRef`の値を変更してもコンポーネントは再レンダリングされませんの。

### **例：useRefの使用方法**

以下に`useRef`を使用したコード例を示しますわ。

```tsx
import { useRef } from "react";

const Lesson3_1 = () => {
  const ref = useRef(0);
  console.log(ref);

  function handleClick() {
    ref.current = ref.current + 1;
    alert(ref.current);
  }
  return (
    <div>
      <input type="text" />
      <button onClick={handleClick}>Click me!</button>
      <p></p>
    </div>
  );
};

export default Lesson3_1;

```

### **説明**

1. **useRefの初期化**:
    `const ref = useRef(0);`で、`ref`という変数に`0`を初期値として保持しますの。
    <br>
2. **handleClick関数**:
    ボタンがクリックされると、`handleClick`関数が実行され、`ref.current`の値が`1`増加しますの。
    
    なんで、`ref`じゃなくて、`ref.current`を操作するのかというと、`ref`オブジェクト自体が参照のコンテナであり、実際の参照されている値やDOMノードは `ref.current` に格納されているからですの。
    
    ![スクリーンショット 2024-06-05 23.53.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2983769/fccc41a9-d686-ac44-8127-2aa363a29362.png)
    
    <br>
3. **レンダリング後も値を保持**:
    `ref.current`はレンダリング後も値を保持し続けますの。例えば、`input`タグに入力をしても、`ref.current`の値は変更されませんの。

![スクリーンショット 2024-06-04 19.39.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2983769/adb5cf93-4892-5a39-3f80-99a51dfcc675.png)

![スクリーンショット 2024-06-04 19.40.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2983769/5a5843a3-0051-fd7d-77fd-7ac7a955c877.png)


inputタグで何か入力し再レンダリングしても、

![スクリーンショット 2024-06-04 19.40.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2983769/6522ada8-82e8-f7ce-e891-1c89275af660.png)

値は保持されていることが、コンソールでわかると思いますの。

## useRefでDOMを取得して画像スクロールを実装してみよう

`useRef` を使用して、DOM要素を参照し、その要素に対して操作を行うことができます。今回は、ボタンを押すと対応する画像までスクロールする機能を実装しますわ。

### **初期コード**

以下は、`useRef`を使って`ul`タグのDOMを取得する基本的なコードですの。

```tsx
import { useRef } from "react";

const Lesson3_2 = () => {
  const listRef = useRef(null);

  const scrollToIndex = () => {
    console.log(listRef);
  };

  return (
    <div>
      <nav>
        <button onClick={() => scrollToIndex()}>Cat1</button>
        <button onClick={() => scrollToIndex()}>Cat2</button>
        <button onClick={() => scrollToIndex()}>Cat3</button>
      </nav>
      <div style={{ overflowX: "auto", maxWidth: "700px", margin: "auto" }}>
        <ul
          className="flex items-center justify-between"
          style={{ minWidth: "1300px" }} // コンテナより大きいサイズを指定
          ref = {listRef}
        >
          <li>
            <img src="https://loremflickr.com/315/240" alt="Cat 1" />
          </li>
          <li>
            <img src="https://loremflickr.com/300/240" alt="Cat 2" />
          </li>
          <li>
            <img src="https://loremflickr.com/280/240" alt="Cat 3" />
          </li>
        </ul>
      </div>
    </div>
  );
};

export default Lesson3_2;

```

### **実装例：ボタンを押すと画像がスクロール**

次に、ボタンを押すと指定した画像までスクロールする機能を実装しますわ。`useRef`を使って`ul`要素を参照し、その子要素である画像にスクロールしますの。

```tsx
import { RefObject, useRef } from "react";

const Lesson3_2 = () => {
  const listRef: RefObject<HTMLUListElement> = useRef<HTMLUListElement>(null);

  const scrollToIndex = (index: number) => {
    console.log(listRef);
    const listNode = listRef.current;
    const imgNode = listNode?.querySelectorAll("li > img")[index];
    console.log(imgNode);

    imgNode?.scrollIntoView({
      behavior: "smooth",
      block: "nearest",
      inline: "center"
    })
  };

  return (
    <div>
      <nav>
        <button onClick={() => scrollToIndex(0)}>Cat1</button>
        <button onClick={() => scrollToIndex(1)}>Cat2</button>
        <button onClick={() => scrollToIndex(2)}>Cat3</button>
      </nav>
      <div style={{ overflowX: "auto", maxWidth: "700px", margin: "auto" }}>
        <ul
          className="flex items-center justify-between"
          style={{ minWidth: "1300px" }} // コンテナより大きいサイズを指定
          ref = {listRef}
        >
          <li>
            <img src="https://loremflickr.com/315/240" alt="Cat 1" />
          </li>
          <li>
            <img src="https://loremflickr.com/300/240" alt="Cat 2" />
          </li>
          <li>
            <img src="https://loremflickr.com/280/240" alt="Cat 3" />
          </li>
        </ul>
      </div>
    </div>
  );
};

export default Lesson3_2;

```
![スクリーンショット 2024-06-05 2.32.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2983769/6bc992a8-73c1-345e-2464-b0d48759991a.png)

### **説明**

1. **useRefの初期化**:
    `const listRef: RefObject<HTMLUListElement> = useRef<HTMLUListElement>(null);` で、`ul`要素を取得しますの。
    <br>
2. **scrollToIndex関数**:
    `scrollToIndex`関数は、リストの特定の画像までスクロールしますの。`listRef.current`から`ul`要素を取得し、その子要素である画像を`querySelectorAll`で取得しますの。
    
    取得した画像要素に対して`scrollIntoView`メソッドを使用し、スムーズにスクロールしますの。
    <br>
3. **DOM要素へのアクセス**:
    `ref`属性を使用して`ul`タグを`listRef`に割り当てますの。これにより、`scrollToIndex`関数内で`listRef.current`を介して`ul`要素にアクセスできますの。

## UseRef()を使って余計な再レンダリングを防ぐ方法

`useRef`を使用することで、コンポーネントの不要な再レンダリングを防ぐことが出来ますの。これにより、パフォーマンスを向上させることができますわ。

### 状態管理による再レンダリング

以下のコードでは、`useState` を使用して、`input`を管理している為、`onChange` イベントが発生されるたびに再レンダリングされますわ。

```tsx
import { useState } from "react";

const Lesson3_3 = () => {

  const [inputText, setInputText] = useState("");

  console.log("render")

  for(let i; i < 50; i++) {
    console.log("aiueo");
  }

  const handleClick = () => {
    alert(inputText);
  };

  return (
    <div>
      <input type="text" className="border-b" value={inputText} onChange={(e) => setInputText(e.target.value)}/>
      <button onClick={handleClick}>input入力値を見る</button>
    </div>
  );
};

export default Lesson3_3;

```

### useRefを使用して再レンダリングを防ぐ

以下のコードでは、`useRef`を使用して`input`の値を管理し、再レンダリングを防いでいますの。

```tsx
import { useRef, useState } from "react";

const Lesson3_3 = () => {

  // const [inputText, setInputText] = useState("");
  const inputRef = useRef<HTMLInputElement>(null);

  console.log("render")

  const handleClick = () => {
    alert(inputRef.current?.value)
  };

  return (
    <div>
      <input type="text" className="border-b" ref={inputRef} />
      <button onClick={handleClick}>input入力値を見る</button>
    </div>
  );
};

export default Lesson3_3;

```

### **説明**

1. **useRefの初期化**:
    `const inputRef = useRef<HTMLInputElement>(null);`で`input`の値を参照しますの。
    <br>
2. **再レンダリングの防止**:
    `useState`を使用せずに、`useRef`を使うことで、`input`の値の変更による再レンダリングを防ぎますの。
    
    `onChange`イベントが発生しても、コンポーネントは再レンダリングされませんの。
    <br>
3. **inputの値の取得**:
    `inputRef.current?.value`を使用して`input`の値を取得しますの。
    
    `handleClick`関数内で、ボタンがクリックされたときに`input`の値をアラートで表示しますの。

## 実際の使用例

`useRef`は、以下のような場合に非常に便利ですわ。

- **パフォーマンスの最適化:**
    - 重い処理が含まれる場合、再レンダリングを防ぐことでパフォーマンスを最適化しますの。
- **DOM要素への直接アクセス:**
    - `input`要素の値を直接参照する場合など、DOM要素に対して直接操作を行いたい場合に使用しますの。
- **ECサイトでの検索機能:**
    - 商品検索の入力フィールドなど、頻繁に更新されるが再レンダリングを避けたい場合に有効ですわ。

## forwardRef()で別のコンポーネントのDOMノードにアクセスする

`forwardRef`を使用すると、親コンポーネントから子コンポーネントに`ref`を渡すことができ、子コンポーネントのDOMノードに直接アクセスできますの。以下に、その実装例を示しますわ。

### **親コンポーネントの実装**

親コンポーネントである`Lesson3_4`から子コンポーネント`MyVideoPlayer`に`ref`を渡し、動画の再生と一時停止を制御しますの。

```tsx
import { useRef } from "react";
import { MyVideoPlayer } from "./MyVideoPlayer";

const Lesson3_4 = () => {
  const videoRef = useRef<HTMLVideoElement>(null);

  return (
    <div>
      <button onClick={() => videoRef.current?.play()}>Play</button>
      <button onClick={() => videoRef.current?.pause()}>Pause</button>
      <br />
      <MyVideoPlayer
        ref={videoRef}
        src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
        type="video/mp4"
        width="250"
      />
    </div>
  );
};

export default Lesson3_4;

```

### **子コンポーネントの実装**

子コンポーネント`MyVideoPlayer`では、`forwardRef`を使用して親コンポーネントから渡された`ref`を受け取りますの。

```tsx
import { forwardRef } from "react";

type MyVideoPlayerProps = {
  width: string;
  type: string;
  src: string;
};

export const MyVideoPlayer = forwardRef<HTMLVideoElement, MyVideoPlayerProps>(
  (props, ref) => {
    return (
      <video width={props.width} ref={ref}>
        <source src={props.src} type={props.type} />
      </video>
    );
  }
);

```

![スクリーンショット 2024-06-05 2.33.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2983769/0c6ffaed-9246-afa7-c99e-07e9705ced97.png)


### 説明

1. **親コンポーネントの設定:**
    `useRef`を使用して`videoRef`を作成し、`MyVideoPlayer`コンポーネントに`ref`として渡しますの。
    
    ボタンのクリックイベントで`videoRef.current?.play()`および`videoRef.current?.pause()`を呼び出して、動画の再生と一時停止を制御しますの。
    <br>
2. **子コンポーネントの設定**:
    `forwardRef`を使用して、子コンポーネント`MyVideoPlayer`が親コンポーネントから渡された`ref`を受け取りますの。
    
    `ref`を`<video>`要素に渡すことで、親コンポーネントから直接`<video>`要素にアクセスできるようにしますの。

## まとめ

<font color="red">**useRefの基本特性:**</font>
    
`useRef`はレンダリング時に不要な値を保持するためのReactフックですの。`useState`とは異なり、値の変更がコンポーネントの再レンダリングを引き起こさないため、パフォーマンスの最適化に有効ですの。
    
<font color="red">**DOM操作の実装例:**</font>
    
`useRef`を使ってDOM要素にアクセスし、その要素に対する操作を行うことができますの。具体例として、ボタンを押すと対応する画像までスムーズにスクロールする機能の実装がありますの。
    
<font color="red">**再レンダリング防止**:</font>

`useState`を使う場合と比べて、`input`の値の変更による再レンダリングを防ぎ、パフォーマンスを向上させることができますの。
    
<font color="red">**実際の使用例**:</font>

`useRef`はパフォーマンスの最適化やDOM要素への直接アクセスが必要な場合に特に有用ですわ。例えば、ECサイトの検索機能など、頻繁に更新されるが再レンダリングを避けたい場面で効果を発揮しますの。
    
<font color="red">**forwardRefの活用**:</font>
`forwardRef`を使用することで、親コンポーネントから子コンポーネントに`ref`を渡し、子コンポーネントのDOMノードに直接アクセスすることが可能ですの。これにより、例えば動画の再生や一時停止を親コンポーネントから制御することができますわ。