3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

まえがき

React Hooks の自分用ざっくりまとめです。
誤りなどがあればご指摘頂けると幸いです。

useState

useState() は関数コンポーネントでの状態(state)を管理するためのフック。
const [state, state更新関数] = useState(初期値) のように定義する。

App.jsx
import { useState } from "react";

/**
 * useState() は関数コンポーネントでの状態(state)を管理するためのフック。
 * const [state, state更新関数] = useState(初期値) のように定義する。
 *
 * setState() は state を更新するために利用できる関数で、
 * state を直接引数で渡す書き方①と
 * 直前の state から新しい state を計算する関数を渡す書き方②がある。
 */

const Counter = () => {
  // 初期値0で count を定義
  const [count, setCount] = useState(0);

  // ① 現在の count に直接 +1 して更新する
  const increment = () => setCount(count + 1);

  // ② 更新直前の count を受け取り、それを +1 して更新する
  const incrementByPreState = () => setCount((prevCount) => prevCount + 1);

  return (
    <>
      <h2>useState</h2>
      <div>Count: {count}</div>
      <button onClick={increment}>Count Up ①</button>
      <button onClick={incrementByPreState}>Count Up ②</button>
    </>
  );
};

export default Counter;

useState.gif
CodeSandBox で見る

useEffect

useEffect() は第1引数に渡された関数(副作用関数)の実行タイミングをレンダリング後まで遅らせるフック。
第2引数に依存配列を渡すことで副作用関数の実行タイミングを制御できる。

App.jsx
import { useEffect, useState } from "react";

/**
 * useEffect() は第1引数に渡された関数(副作用関数)の実行タイミングをレンダリング後まで遅らせるフック。
 * 第2引数に依存配列を渡すことで副作用関数の実行タイミングを制御できる。
 */

const App = () => {
  const [users, setUsers] = useState([]);

  // 依存配列が空の useEffect() でマウント後に1度のみ副作用(関数)を実行
  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((res) => res.json())
      .then((users) => setUsers(users));
  }, []);

  return (
    <>
      <h1>ユーザー一覧</h1>
      <ul>
        {users.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </>
  );
};

export default App;

useEffect.gif
CodeSandBox で見る

useEffect + Dependency Array

useEffect に依存配列を指定すると、
指定した値に変更があった場合に副作用関数を実行する。

App.jsx
import { useEffect, useState } from "react";

/**
 * useEffect に依存配列を指定すると、
 * 指定した値に変更があった場合に副作用関数を実行する。
 */

const Counter = () => {
  const [count, setCount] = useState(0);

  // 依存配列に count を指定しているため、Count Upボタンを押下するたびに
  // count の値が変動し、副作用関数が実行されログが出力される。
  useEffect(() => {
    console.log("Count Up");
  }, [count]);

  return (
    <>
      <h2>useEffect(Dependency Array)</h2>
      <div>Count: {count}</div>
      <button onClick={() => setCount(count + 1)}>Count Up</button>
    </>
  );
};

export default Counter;

useEffect(DependencyArray).gif
CodeSandBox で見る

useEffect + Cleanup Function

useEffect() の副作用関数内で return を書くことでクリーンアップ関数を作成できる。
クリーンアップ関数を return することで2回目以降のレンダリング時に前回実行した
副作用を打ち消すことができ、タイマーのキャンセルやイベントリスナーの削除などで利用できる。

Timer.jsx
import { useEffect, useState } from "react";

/**
 * useEffect の副作用関数内で return を書くことでクリーンアップ関数を作成できる。
 * クリーンアップ関数を return することで2回目以降のレンダリング時に前回実行した
 * 副作用を打ち消すことができ、タイマーのキャンセルやイベントリスナーの削除などで利用できる。
 */

const Timer = () => {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const id = setInterval(() => {
      console.log("working", count);
      setCount(count + 1);
    }, 1000);

    // 親コンポーネントで制御している Timer コンポーネントの表示・非表示時に
    // clearInterval を実行してログ出力がアンマウント後に止まるようにしている。
    return () => {
      clearInterval(id);
    };
  });
  return <h2>Timer Counts: {count}</h2>;
};

export default Timer;

App.jsx
import { useState } from "react";
import Timer from "./Timer";

const App = () => {
  const [show, setShow] = useState(true);

  return (
    <>
      {/* Timer コンポーネントは表示中にログを出力し続ける機能を有している。 */}
      {show && <Timer />}
      <button onClick={() => setShow(!show)}>Toggle</button>
    </>
  );
};

export default App;

useEffect(CleanUp).gif
CodeSandBox で見る

useEffect + Cleanup + EventListener

addEventListerner() でイベントをバインドした場合、マウントのたびにイベントが多重にバインドされてしまわないようにするため、 removeEventListerner() をクリーンアップ時に実行する。

import { useEffect, useState } from "react";

const App = () => {
  const [windowWidth, setWindowWidth] = useState(0);

  useEffect(() => {
    function handleResize() {
      setWindowWidth(window.innerWidth);
    }

    // 画面描画時に画面幅情報を取得
    handleResize();
    // 画面リサイズ時に画面幅情報を再取得
    window.addEventListener("resize", handleResize);

    // クリーンアップで resize イベントへのバインドを解除し、多重にバインドされないようにする。
    return () => window.removeEventListener("resize", handleResize);
  }, []);

  return (
    <>
      <h1>useEffect + Cleanup (EventListener)</h1>
      <p>windowWidth: {windowWidth}</p>
    </>
  );
};

export default App;

useEffect(CleanUpEventListener).gif
CodeSandBox で見る

useEffect + async/await

useEffect() の第1引数の副作用関数は戻り値無し or クリーンアップ関数を設定する必要があり、
非同期関数を設定すると戻り値が Promise型 となりエラーになるため、
副作用関数内で非同期関数を定義して利用するか、非同期の即時関数を使って実装する。

App.jsx
import { useEffect, useState } from "react";

/**
 * useEffect の第1引数の副作用関数は戻り値無し or クリーンアップ関数を設定する必要があり、
 * 非同期関数を設定すると戻り値が Promise型 となるためエラーになるため
 * 副作用関数内で非同期関数を定義して利用するか、非同期の即時関数を使って実装する。
 */

const App = () => {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    // 非同期の関数を定義するパターン
    const fetchUsers = async () => {
      const res = await fetch("https://jsonplaceholder.typicode.com/users");
      const users = await res.json();
      setUsers(users);
    };
    fetchUsers();

    // 非同期の即時関数で実装するパターン
    // (async() => {
    //   const res = await fetch("https://jsonplaceholder.typicode.com/users");
    //   const users = await res.json();
    //   setUsers(users);
    // })()
  }, []);

  return (
    <>
      <h2>useEffect(async/await)</h2>
      <ul>
        {users.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </>
  );
};

export default App;

useEffect(asyncAwait).gif
CodeSandBox で見る

useRef

useRef() は画面の要素への参照を実行し、再レンダリングを行わずに保持している値を更新することができる。(stateの更新時は再レンダリングされる)

App.jsx
import { useRef } from "react";

// useRef は画面の再レンダリングを行わずに保持している値を更新することができる。(stateの更新時は再レンダリングされる)

const App = () => {
  // useRef は .current プロパティが渡された引数を inputRef へ返す。
  const inputRef = useRef(null);

  const handleClick = () => {
    inputRef.current.value = "useRef"; // input の値を書き換え
    inputRef.current.focus(); // input にフォーカス
    console.log(inputRef.current);
  };

  return (
    <>
      <h1>useRef</h1>
      <div>
        {/* ref={inputRef} でDOMの参照をする */}
        <input type="text" ref={inputRef} />
      </div>
      <button onClick={handleClick}>Focus Input</button>
    </>
  );
};

export default App;

useRef.gif
CodeSandBox で見る

useRef + useState

App.jsx
import { useRef, useState } from "react";

const App = () => {
  const inputRef = useRef("");
  const [text, setText] = useState("");

  // useRef  で参照している input の値更新時は再レンダリングされないため、
  // useState での state 更新時のみ再レンダリングされログが出力される。
  console.log("Rendering!");

  return (
    <>
      <h1>useRef + useState</h1>
      <div>
        <input ref={inputRef} type="text" />
      </div>
      <button onClick={() => setText(inputRef.current.value)}>
        Set Text by useState
      </button>
      <p>Text: {text}</p>
    </>
  );
};

export default App;

useRef(useState).gif
CodeSandBox で見る

useRef + form

useRef() でフォーム要素を参照しデータを保持/更新することで、 useState() を使わなくても再レンダリングを行わずにフォームを実装することができる。

App.jsx
import { useRef } from "react";

const App = () => {
  // useRef で DOM への参照を生成
  const emailRef = useRef(null);
  const passwordRef = useRef(null);

  const handleSUbmit = (e) => {
    e.preventDefault();
    console.log(
      `email: ${emailRef.current.value}, password: ${passwordRef.current.value}`
    );
  };

  return (
    <div>
      <h1>Form with useRef</h1>
      <form onSubmit={handleSUbmit}>
        <div>
          <label htmlFor="email">Email</label>
          <input id="email" type="text" ref={emailRef} />
        </div>
        <div>
          <label htmlFor="password">Password</label>
          <input id="password" type="password" ref={passwordRef} />
        </div>
        <div>
          <button type="submit">LOGIN</button>
        </div>
      </form>
    </div>
  );
};

export default App;

useRef(withForm).gif
CodeSandBox で見る

useReducer

useReducer() は状態管理のためのフックで、 useState() を内部実装している。
(state, action) => newState という型の reducer を受け取り、
現在の state とそれを更新するための dispatch 関数を返す。

App.jsx
import { useReducer } from "react";

// 初期値を定義
const initialState = {
  count: 0,
  actionCount: 0
};

// state を更新するための関数で、 dispatch で呼び出して利用する。
// reducer では複数の state を同時に更新することもできる。
const reducer = (state, action) => {
  switch (action) {
    case "INCREMENT":
      return {
        count: state.count + 1,
        actionCount: state.actionCount + 1
      };
    case "DECREMENT":
      return {
        count: state.count - 1,
        actionCount: state.actionCount + 1
      };
    case "RESET":
      return {
        count: 0,
        actionCount: 0
      };
    default:
      return state;
  }
};

const App = () => {
  /**
   * useReducer は状態管理のためのフックで、 useState を内部実装している。
   * (state, action) => newState という型の reducer を受け取り、
   * 現在の state とそれを更新するための dispatch 関数を返す。
   */
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <>
      <h1>useReducer</h1>
      <p>Count: {state.count}</p>
      {/* dispatch('actionType') で action を指定し実行する */}
      <button onClick={() => dispatch("INCREMENT")}>INCREMENT</button>
      <button onClick={() => dispatch("DECREMENT")}>DECREMENT</button>
      <p>Action Count: {state.actionCount}</p>
      <button onClick={() => dispatch("RESET")}>RESET</button>
    </>
  );
};

export default App;

useReducer.gif
CodeSandBox で見る

useContext

useContext() を利用することで props を子コンポーネントにバケツリレーする必要がなくなる。
Provider - データを渡す側: createContext() で生成し Consumer をラップする
Consumer - データを受け取る側: useContext(contextName) でデータを利用できる

App.jsx(Provider)
import { createContext } from "react";
import Content from "./Content";

/**
 * useContext を利用することで props を子コンポーネントにバケツリレーする必要がなくなる。
 *
 * Provider: データを渡す側 createContext() で生成し Consumer をラップする
 * Consumer: データを受け取る側 useContext(contextName) でデータを利用できる
 */

// createContext で Context オブジェクトを生成する。(初期値を入れることも可能)
export const UserContext = createContext();

const App = () => {
  // Consumer(子コンポーネント) で利用する値を定義
  const user = {
    name: "Michael Jakson",
    age: 50
  };
  return (
    // Provier の value に定義した値をセット
    <UserContext.Provider value={user}>
      <h1>useContext</h1>
      {/* Consumer を Provider 内部に記載することで value を利用できる */}
      <Content />
    </UserContext.Provider>
  );
};

export default App;

Content.jsx
import Name from "./Name";
import Age from "./Age";

// useContext を利用しているため、子コンポーネントへの props バケツリレーが不要!
const Content = () => {
  return (
    <>
      <Name />
      <Age />
    </>
  );
};

export default Content;

Name.jsx(Consumer)
import { useContext } from "react";
import { UserContext } from "./App";

const Name = () => {
  const { name } = useContext(UserContext);
  return <p>Name: {name}</p>;
};

export default Name;

useContext.gif
CodeSandBox で見る

useCallback

useCallback() で関数をメモ化することでコンポーネントの再レンダリング時に関数の不要な再生性を防ぐ。
親コンポーネントから渡される props に変更がない場合にラップしたコンポーネントの再レンダリングをスキップすることができる memo() と組み合わせて利用することで、不要なレンダリングを抑制することができる。

メモ化: 同じ結果を返す処理の結果を保持しておき、2回目以降の処理時は都度計算するのではなく保持した値を参照すること。

App.jsx
import { useCallback, useState } from "react";
import Reset from "./Reset";

/**
 * useCallback で関数をメモ化することでコンポーネントの再レンダリング時に関数の不要な再生性を防ぎ、
 * memo と合わせて利用することでその関数を props として受け取っている子コンポーネントの不要な再レンダリングを防ぐことができる。
 *
 * memo は親コンポーネントから渡される props に変更がない場合に
 * コンポーネントの再レンダリングをスキップすることができる。
 *
 * ここでは resetCount() を useCallback でラップし、
 * memo() でメモ化したResetコンポーネントに渡して再レンダリングを抑制する。
 */

const App = () => {
  const [count, setCount] = useState(0);

  const increment = () => setCount(count + 1);

  // increment 時に state が更新され再レンダリングが走るが、
  // resetCount() は useCallback を利用しているため再生成されない。
  const resetCount = useCallback(() => {
    setCount(0);
  }, []);

  // 依存配列を指定するとその対象の値が変化した際に再生性が実行される。
  // 以下のようにすると count の変化時に resetCount() が再生成され、 useCallback を利用していない場合と同じ挙動になる。
  // const resetCount = useCallback(() => {
  //   setCount(0);
  // }, [count]);

  // useCallback でラップしていない場合は resetCount() も再生性されてしまう。
  // const resetCount = () => setCount(0);

  return (
    <>
      <h1>useCallback</h1>
      <p>Count: {count}</p>
      <button onClick={increment}>INCREMENT</button>
      <Reset resetCount={resetCount} />
    </>
  );
};

export default App;

Reset.jsx
import { memo } from "react";

console.log("Reset");

// memo() でラップしmemo化しているため、 props が変更されない場合にレンダリングがスキップされる
const Reset = memo(({ resetCount }) => {
  console.log("Render Reset Component!");
  return <button onClick={resetCount}>RESET</button>;
});

export default Reset;

useCallback.gif
CodeSandBox で見る

useMemo

useMemo() で関数の結果をメモ化でき、不要な再計算をスキップすることでパフォーマンス向上を図れる。
useCallback() が関数自体をメモ化するのに対して useMemo() は関数の結果をメモ化する。

App.jsx
/**
 * useMemo で関数による計算結果をメモ化できる。
 * 依存配列を指定するとその値に変更があった場合に useMemo が再計算される。
 */

import { useMemo, useState } from "react";

const App = () => {
  const [countA, setCountA] = useState(0);
  const [countB, setCountB] = useState(0);

  const incrementA = () => setCountA(countA + 1);
  const incrementB = () => setCountB(countB + 1);

  // useMemo でラップし、 countA に変更があった場合のみ doubleCountA を再計算する。
  const doubleCountA = useMemo(() => {
    console.log("doubleCountA"); // incrementB 実行時は呼ばれない
    return countA * 2;
  }, [countA]);

  return (
    <>
      <h1>useMemo</h1>
      <p>Count A: {countA}</p>
      <button onClick={incrementA}>Count Up A</button>
      <p>Count B: {countB}</p>
      <button onClick={incrementB}>Count Up B</button>
      <p>Double Count A: {doubleCountA}</p>
    </>
  );
};

export default App;

useMemo.gif
CodeSandBox で見る

useMemo + memo

useMemo() で関数の計算結果をメモ化し、 メモ化したコンポーネントに props で渡すことで不要な再レンダリングをスキップする。

App.jsx
/**
 * useMemo で関数による計算結果をメモ化して
 * memo化した子コンポーネント Count に props として渡すことで
 * 不要な再計算&再レンダリングを抑制することができる。
 */

import { useMemo, useState } from "react";
import Count from "./Count";

const App = () => {
  const [countA, setCountA] = useState(0);
  const [countB, setCountB] = useState(0);

  const incrementA = () => setCountA(countA + 1);
  const incrementB = () => setCountB(countB + 1);

  // useMemo でラップし、 countA に変化がない場合に doubleCountA が再計算されないようにする。
  const doubleCountA = useMemo(() => {
    return countA * 2;
  }, [countA]);

  return (
    <>
      <h1>useMemo + memo</h1>
      <p>Count One:{countA}</p>
      <button onClick={incrementA}>INCREMENT A</button>
      <p>Count Two:{countB}</p>
      <button onClick={incrementB}>INCREMENT B</button>
      <Count doubleCountA={doubleCountA} />
    </>
  );
};

export default App;

Count.jsx
import { memo } from "react";

// memo でラップし、 props(doubleCountA) に変化がない場合の再レンダリングを抑制する。
const Count = memo(({ doubleCountA }) => {
  console.log("Render Count Component!");
  return <p>Double Count A: {doubleCountA}</p>;
});

export default Count;

useMemo(withMemo).gif
CodeSandBox で見る

customHook

use から始まる名前でカスタムフック(自分独自のフック)を作成することで、
コンポーネントからロジックを抽出して再利用可能な関数を作成できる。
ここではサンプルとして画面サイズ情報取得・更新ロジックを共通化して切り出す。

App.jsx
import { useResize } from "./useResize";

const App = () => {
  // カスタムフック useResize を呼び出す
  const windowSize = useResize();
  return (
    <>
      <h1>コンテンツ領域のサイズ</h1>
      <p>ブラウザのコンテンツ領域の幅: {windowSize.width}px</p>
      <p>ブラウザのコンテンツ領域の高さ: {windowSize.height}px</p>
    </>
  );
};

export default App;

useResize.jsx
import { useEffect, useState } from "react";

/**
 * カスタムフックを作成し、画面サイズ情報取得・更新ロジックを共通化して切り出す。
 * 基本的にカスタムフックの命名は use から始まるようにする。
 */

export const useResize = () => {
  const [windowSize, setWindowSize] = useState({
    width: undefined,
    height: undefined
  });

  useEffect(() => {
    function handleResize() {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    }

    handleResize();
    window.addEventListener("resize", handleResize);

    return () => window.removeEventListener("resize", handleResize);
  }, []);

  return windowSize;
};

customHook(useResize).gif
CodeSandBox で見る


参考文献

3
2
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
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?