LoginSignup
58
42

More than 1 year has passed since last update.

React Hooksってなんぞ

Last updated at Posted at 2021-08-20

はじめに

社内でReact Hooksとはなんぞやの勉強会が行われたので、議事録兼アウトプットとして記事を書きます。

React Hooksとは

React16.8で導入された機能であり、関数コンポーネントにおいて、ステート管理、ライフサイクルといった機能を使えるようにしてくれるものです。

React Hooksが生まれた背景

Reactにはクラスコンポーネント関数コンポーネントの2種類の書き方が存在します。
以下にクラスコンポーネントの書き方でボタンで文言をtoggleさせるコード例を示します。

import React from "react";

class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      isLogin: true
    }
  }

  toggleLoginStatus = () => {
    this.setState({isLogin: !this.state.isLogin});
  }

  render() {
    return (
      <>
        {this.state.isLogin ? "ログイン中" : "ログアウト中"}
        <button onClick={this.toggleLoginStatus}>toggle</button>
      </>
    )
  }
}

export default Hoge

ボタンを押す事でisLoginの状態を変化させ、文言の出しわけを行っています。
ひと昔前は、クラスコンポーネントを使ってコードを記述するのが一般的だったそうです。
なぜかというと、クラスコンポーネントではステート管理、ライフサイクルといった機能が使えますが、関数コンポーネントではそれらを使うことができなかったからです。

そこで登場したのがReact Hooksというものです。
React Hooksが登場したことにより、関数コンポーネントで状態管理やライフサイクル機能が使えるとようになり、可読性の高いコードがかけるようになりました。
以下に先ほどの例と同じものを関数コンポーネントで書いたバージョンを示します。

import { useState } from "react";

const Hoge = () => {
  const [isLogin, setIsLogin] = useState(true);

  const toggleLoginStatus = () => {
    setIsLogin(isLogin => !isLogin);
  }

  return (
    <>
      {isLogin ? "ログイン中" : "ログアウト中"}
      <button onClick={toggleLoginStatus}>toggle</button>
    </>
  );
}

export default Hoge

クラスコンポーネントに比べてより完結に同じ処理が記述ができることが見てわかるかと思います。

React Hooksの紹介

簡単にですが、React Hooksの紹介をします。

useState

useStateはステートを保持するために使われるフックで、React Hooksの中で一番よく使われます。
numberやboolean、mapなど様々な状態を保持することができます。
ボタンを押すとカウントが増えていくコード例を以下に示します。

import { useState } from "react";

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

  const countUp = () => {
    setCount(count => count + 1);
  }

  return (
    <>
      <p>今のカウント:{count}</p>
      <button onClick={countUp}>
        count up!
      </button>
    </>
  );
}

export default Hoge

useEffect

useEffectはライフサイクル(クラスコンポーネントでいうところの、componentDidMountcomponentDidUpdate, componentWillUnmount)を実現させるためのフックで、レンダリング時やステートを更新するタイミングでuseEffect内の副作用関数を走らせることができます。

初回のレンダリング時のみ処理を実行させたいとき

useEffectの第二引数に空の依存配列を指定します。

// レンダリングされた時にのみ実行(componentDidMount)
  useEffect(() => {
    console.log("component did mount");
  }, []);
特定のステートが更新された時に処理を実行させたいとき

useEffectの第二引数にウォッチさせたいステートを指定します。

// countが更新された時に実行(componentDidUpdate)
  useEffect(() => {
    console.log("component did update");
    console.log(`current count is ${count}`);
  }, [count])
コンポーネントを削除(アンマウント時)について

useEffectの中で関数をreturnすることで、マウント時に実行した副作用関数を削除します。
これを行うことで予期せぬバグを回避することができます。
returnする関数はクリーンアップ関数と呼ばれます。

// コンポーネントが削除される際に実行(componentWillUnmount)
  useEffect(() => {
    return () => {
      console.log("component will unmount");
    };
  }, [])

以上をまとめたものがこちら

import { useState, useEffect } from "react";

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

  const countUp = () => {
    setCount(count => count + 1);
  }

  useEffect(() => {
    console.log("component did mount");
  }, []);

  useEffect(() => {
    console.log("component did update");
    console.log(`current count is ${count}`);
  }, [count])

  useEffect(() => {
    return () => {
      console.log("component will unmount");
    };
  }, [])

  return (
    <>
      今のカウント:{count}
      <button onClick={countUp}>
        count up!
      </button>
    </>
  );
}

useContext

親コンポーネントから子コンポーエントに値を渡したいときが多々あるかと思いますが、親コンポーネントから孫コンポーネントへ値を渡したいときも場合によってはあるかと思います。
そんな時に、親から子、子から孫へとpropsを渡すのではなく、useContextを使えば、Contextに格納されている値をどの階層でも参照することができます。
便利である一方、Contextを更新したら、そのContextを利用しているすべてのコンポーネントが再レンダリングされてしまうので、注意が必要です。
親コンポーネントで定義したContextを孫コンポーネントで取得するコードを以下に示します。

import { useContext, createContext } from "react";

const user = {
  name: "Taro",
  age: 17
}

const UserContext = createContext(user);

const Hoge = () => {
  return (
    <UserContext.Provider value={user}>
      <User />
    </UserContext.Provider>
  );
}

const User = () => {
  return(
    <UserProfile />
  )
}

const UserProfile = () => {
  const userInfo = useContext(UserContext);
  return (
    <>
      <p>名前:{userInfo.name}</p>
      <p>年齢:{userInfo.age}</p>
    </>
  );
}

export default Hoge

useReducer

useReducerはuseStateと同じく、ステートを管理するフックで、複数の値や条件によってstateの更新を行いたいとき時に主に使われます。
(state, action) => newStateの型をもったreducerを受け取り、現時点でのstateとdispatchを返します。
actionとなる引数を持ったdispatchメソッドをonClick等で実行することによって、reducerが呼び出され、actionによってstateの更新が行われます。
あくまでnewStateをreturnするので値単体ではなく、オブジェクトで定義していたらオブジェクトとしてreturnしなければいけません。

import { useReducer } from "react";

const reducer = (state: any, action: any) => {
  switch (action) {
    case "increment":
      return { count: state.count + 1 }
    case "decrement":
      return { count: state.count - 1 }
    default:
      throw new Error();
  }
}

const Hoge = () => {
  const [countState, dispatch] = useReducer(reducer, { count: 0 });
  return (
    <>
      <p>今のカウント:{countState.count}</p>
      <button onClick={() => dispatch("decrement")}>decrement</button>
      <button onClick={() => dispatch("increment")}>increment</button>
    </>
  );
}

export default Hoge

useCallback

useCallbackを使うことで、関数をメモ化し、コールバック関数として返すことにより余計な再レンダリングを防ぐことができます。
useCallbackはReact.memoと使われることが多いです。

React.memoとは

React.memoとはメモ化したいコンポーネントをラップするときに用いられます。
メモ化とは、propsに差分がなければ再描画をスキップするために前回描画したコンポーネント結果をメモしておくことをいいます。

ただし、propsとしてコールバック関数を受け取る場合は、React.memoを使っていたときでも再描画されてしまいます。それは関数の内容は同じでも、コンポーネントが再描画されるたびに関数は生成されるので、前回と等価として扱われないためです。
そこでuseCallbackを使い、コールバック関数をメモ化させることで不要な再描画を防ぐことができます。
以下に、React.memoでメモ化したコンポーネントにuseCallbackでメモ化したコールバック関数を渡すコード例を示します。
この結果、カウントをインクリメント(ステートの更新)をすると、押されたほうだけのコンポーネントのみを再描画させられるようになります。

import { React.memo, useCallback } from "react";

const Button = React.memo(({text, handleClick}) => {
  // 確認用
  console.log(text);
  return (
    <button onClick={handleClick}>{text}</button>
  );
});

const Hoge = () => {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  const handleClick1 = useCallback(() => setCount1(count1 + 1), [count1]);
  const handleClick2 = useCallback(() => setCount2(count2 + 1), [count2]);

  return (
    <>
      <p>count1:{count1}</p>
      <p>count2:{count2}</p>
      <Button text={"count1:increment"} handleClick={handleClick1} />
      <Button text={"count2:increment"} handleClick={handleClick2} />
    </>
  );
}

export default Hoge

useMemo

useMemoは関数の結果を保存しておくためのフックで、再描画の際に不要な再計算を行わないことで、パフォーマンス向上を図ることができます。
先ほどのuseCallBackは関数自体をメモ化し、useMemoは関数の結果をメモ化します。
useMemoを使って計算結果を保持しておき、依存するステートが更新されたときのみに再計算するコード例を以下に示します。

import { useMemo } form "react";

const Hoge = () => {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  const twiceCount1 = useMemo(() => {
    return 2 * count1;
  }, [count1]);

  const handleClick1 = () => setCount1(count1 => count1 + 1);
  const handleClick2 = () => setCount2(count2 => count2 + 1);

  return (
    <>
      <p>count1:{count1}</p>
      <p>count2:{count2}</p>
      <button onClick={handleClick1}>count1:increment</button>
      <button onClick={handleClick2}>count2:increment</button>
      <p>count1:{twiceCount1}</p>
    </>
  );
}

export default Hoge

useRef

useRefはuseStateと同じく、コンポーネントの中で値を保持することができます。
useStateと異なる点は、値を更新するときに再描画を発生させないことです。
なので、ユースケースとしては描画に関係のない値を扱うときに使われます。

使用例として前回のステートを取得するコードを示します。 ※公式を参照
描画した後にuseEffectを用いて、prevCountに前回のステートを保持することができ、次回の再描画のタイミングでprevCountを表示します。

import { useRef } from "react";

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

  const prevCountRef = useRef(0);
  useEffect(() => {
    prevCountRef.current = count;
  });
  const prevCount = prevCountRef.current;

  return (
    <>
      <p>今のカウント:{count}</p>
      <p>前回のカウント:{prevCount}</p>
      <button onClick={() => setCount(count+1)}></button>
    </>
  )
}

export default Hoge

おわりに

React Hooksが生まれた経緯と簡単にですが公式Hooksの紹介をしました。
自分自身、useCallbackやuseMemo,React.memoをあまり意識して使えていなく、今回の議事録アウトプットで基本的な概念を再確認することができました。
今後の開発やリファクタリング等でパフォーマンスを意識したコードをかけるよう精進して参ります。

58
42
1

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
58
42