9
1

More than 1 year has passed since last update.

代表的なReact hooksについて改めてまとめてみた

Last updated at Posted at 2022-12-07

はじめに

6月に未経験から開発エンジニアとして転職し、はや半年が過ぎました。
記事を書こう書こうと思い気づくと12月になっていたので、
アドベントカレンダーに参加し、転職後初の記事投稿をさせて頂きます。

業務でReactを扱う中で、hooksへの理解が足りてないと感じたので
すでにまとめられている記事もありますが、理解を深めるという意味も込めてReact hooksに関して改めてまとめました。

React hooksとは

公式ドキュメントには下記の様にあります。

フック (hook) は React 16.8 で追加された新機能です。state などの React の機能を、クラスを書かずに使えるようになります。
参考:https://ja.reactjs.org/docs/hooks-intro.html

具体的にどう変わったのかは、こちらの記事の説明が非常にわかりやすかったのですが、
要約すると、フックを使ってクラスコンポーネントではなく関数コンポーネントで書くことで、
簡潔に、再利用性の高いコードを書けるようになったということです。

今回は基本のフック3つを含む代表的なフックを紹介します。

useState

まずはサンプルのコードです。
下記はボタンをクリックするとカウンターの値が増えていくというコードです。

useState
import React, { useState } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

下記は動作イメージです。
useState.gif

useStateでは、const [state, stateを更新する関数] = useState(初期値);のように宣言します。
上記ではstateにcount、更新する関数にsetCount初期値に0をそれぞれ宣言しており、
return内で、ボタンをクリックするとcountの値が一つずつ増えるという処理を実行しています。

このように、useStateではstateの保持と管理を行うことができます。

useEffect

次にuseEffectです。
下記のコードでは、ReactがDOMを更新した後に、console.logでメッセージを出力します。

useEffect
import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log(`You clicked ${count} times`);
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

動作は先程と同じですが、DOMが更新されるたび(クリックが押されてcountの値が変化するたび)に、
useEffect内のconsole.logが実行されていることがわかります。

useEffect.gif

このように、useEffectに書いた処理はレンダリングが行われたタイミングで実行することができます。
言い換えると、レンダリングの後まで実行タイミングを遅らせることができます。
useEffectを使用することで、レンダリングが行われた後に何かの操作をすることなしに
変数の更新や、データの取得を行うことができます。

先ほどの例ではuseEffectに第一引数のみ渡していましたが、
第二引数に配列を渡すことで、実行のタイミングを指定することができます。

// 空の配列を渡すと初回の画面レンダリング時のみconsole.logが実行される
useEffect(() => {
  console.log(`You clicked ${count} times`);
}, []);

// 「count」を渡すと最初のレンダリング時と、countの値が変更されたときのみconsole.logが実行される
useEffect(() => {
  console.log(`You clicked ${count} times`);
}, [count]);

useContext

useContextを説明するために、まずはReactにおけるPropsの渡し方について理解する必要があります。
以下のコードでは Button コンポーネントのthemeをスタイルする為に、
Appでtheme="dark"という値をToolbarコンポーネントに渡し、
ToolbarコンポーネントからThemeButtonコンポーネントへthemeをバケツリレーのように渡しています。

Propsを使ったthemeプロパティの渡し方
class App extends React.Component {
  render() {
    return <Toolbar theme="dark" />;
  }
}

function Toolbar(props) {
  // Toolbar コンポーネントは外部から "theme" プロパティを受け取り、
  // プロパティを ThemedButton へ渡します。
  // アプリ内の各ボタンがテーマを知る必要がある場合、
  // プロパティは全てのコンポーネントを通して渡される為、面倒になります。
  return (
    <div>
      <ThemedButton theme={props.theme} />
    </div>
  );
}

class ThemedButton extends React.Component {
  render() {
    return <Button theme={this.props.theme} />;
  }
}

useContextを使用することで、バケツリレーを避けることができます。

useContextを使ったthemeプロパティの渡し方
// コンテクストを使用すると、全てのコンポーネントを明示的に通すことなしに
// コンポーネントツリーの深くまで値を渡すことができます。
// 現在のテーマ(デフォルトは "light")の為のコンテクストを作成します。
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // 以下のツリーへ現在のテーマを渡すためにプロバイダを使用します。
    // どんな深さでも、どのようなコンポーネントでも読み取ることができます。
    // このサンプルでは、"dark" を現在の値として渡しています。
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// 中間のコンポーネントはもう明示的にテーマを
// 下に渡す必要はありません。
function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // React は上位の最も近いテーマプロバイダを見つけ、その値を使用します。
  // この例では、現在のテーマは "dark" です。
  const theme = useContext(ThemeContext);
  render() {
    return <Button theme={this.theme} />;
  }
}

このように、useContextを使いたいときは、
React.createContextでContextの器を作成する。
②作成したContextのProviderで、Stateを渡したいコンポーネントを囲む
③Stateを参照したいコンポーネントでReat.useContextを使う

といった流れになります。

useContextを使用するとシンプルな実装でグローバルなState管理を行うことができますが、
コンテクストはコンポーネントの再利用をより難しくする為、慎重に利用する必要があります。

useCallback

まずはサンプルコードです。

useCallback
const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

useCallbackでは、useEffectと同様第二引数に依存配列をとり、メモ化したコールバック関数を返します。
メモ化とは、前回の処理結果を保持しておくことで処理を高速化する技術で、キャッシュのようなイメージです。
上記の例では、memoizedCallbackdoSomething(a, b)を実行しますが、
依存配列の要素であるaかbが変化しない限り、メモ化したdoSomething(a, b)を返します。

このようにuseCallbackを使用し不要な再描画を減らすことで、パフォーマンスの向上が期待できます。

useMemo

最後にuseMemoについて説明します。

useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

useMemoでもuseCallbackと同様、第二引数に依存配列を取りますが、
useCallbackではメモ化したコールバック関数そのものを返すのに対し、
useMemoでは関数の結果をメモして返します。

上記の例では、aもしくはbの値が変化したときのみ、computeExpensiveValue(a, b)を再実行し、
それ以外のときは前回のcomputeExpensiveValue(a, b)の結果をそのまま返します。

useMemoの第一引数にはコンポーネントを渡すこともでき、
その場合は依存配列の値が変化したときのみコンポーネントを再レンダリングします。

useMemoもuseCallbackと同様、不要な再描画を減らすことで、
パフォーマンスの向上が期待できます。

useCallbackとuseMemoの違いについて

useCallbackとuseMemoはどちらもパフォーマンス最適化のために使うものであり、
逆にいうと使わなくても動作はします。
また、上でも書いたようにuseCallbackは関数そのものを返すのに対し、
useMemoでは関数の結果を返します。
これは、useCallback(fn, deps) は useMemo(() => fn, deps) と等価であるということを意味します。

関数コンポーネントにおいて、レンダリングが発生するとコンポーネント内で定義されたアロー関数は再作成されます。
そのため、useMemoでメモ化したコンポーネントにコールバック関数を渡している場合は注意が必要です。
アロー関数がレンダリングによって再作成された結果、依存配列のコールバック関数が変化したとみなされ、
メモ化したコンポーネントは再利用されません。

そのような関数に対しuseCallbackを使うことで関数の再生成をスキップでき、
コンポーネントのメモ化を実現することができます。

まとめ

今回は主要なReact hooksについてまとめましたが、hooksを利用することでコードをシンプルにかけたり、
パフォーマンス向上を図ることができるとわかりました。
ここでは基本的なhooksのみをとりあげましたが、今後はカスタムフックなども学習し
Reactへの理解を深めていきたいと思います。

参考

9
1
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
9
1