2020年7月19日更新

React Hooks(フック)って何?メリットや使い方についてまとめてみた

Thumbnail

今回の記事では、React Hooks(フック)の知っておきたい情報やさまざまなフック、基本的な使い方についてまとめてみたいと思います。

Reactフックって何?

まずはReactフックがどういったものなのか、見ていきましょう。


React Hooksは2019年2月に、Reactのバージョン16.8から追加された機能です。

Reactフックによって、これまでクラスを使わないといけなかったステート管理やライフサイクルメソッドなどが、関数コンポーネントでも実装できるようになりました。

React Hooksのメリット

Reactフックのメリットを挙げてみると、個人的には次のようになるんじゃないかと思います。

Reactフックのメリット

  • 複雑になりがちなクラスコンポーネントを減らせる
  • propsのバケツリレーへの対策
  • コンポーネント間でステートを共有しやすくなる
  • (ライフサイクルメソッドの分だけ)学習コストが下がる

React Hooksを使うときのルール

Reactフックを利用するためには、次の2つのルールがあります。

React Hooksを使うときのルール

  1. 関数コンポーネントで使う(クラスコンポーネントでは使えない)
  2. if文やループ、入れ子になった関数の中で定義できない

それでは、まずは基本的なReactフックから見ていきましょう。

基本的なReact Hooks

Reactフックには現在いろいろなフックがありますが、その中でも次の3つがもっとも基本となるフックだと言われています。それぞれ詳しく見ていきましょう????

基本となるフック

  1. useState
  2. useEffect
  3. useContext

useState

useStateはクラスコンポーネントのthis.state.○○this.setState({ ○○ })を使わずにステートを扱えるようになるフックです。
わざわざクラスを作らなくても、関数コンポーネントでステートを扱えるようになります。

useStateの使い方

useStateでは次の2つをセットで使用します。
下のコードで言うと、useStateではcountというステートの値と、setCountというcountの値を更新する関数が必要になります。

useStateで必要となるもの

  1. ステートの値
  2. その値を更新するセッター関数

useState()にはステートの初期値を渡す

useState()ではステートの初期値を引数として渡すことが必要になります。
次のコードで言うところのuseState(0)の0がステートの初期値です。

JS
コピーする
const [count, setCount] = useState(0);

ステートが文字列の場合には空の文字列を渡したり、nullを渡すこともあります。

JS
コピーする
const [name, setName] = useState('');
const [isOnline, setIsOnline] = useState(null);
useStateの初期値に変数を使うとき

useState()に渡す値はもちろん変数でも大丈夫なので、initialStateというコメント的な変数を使って次のように書くこともできます。

JS
コピーする
const initialState = 0
const [count, setCount] = useState(initialState)

ちなみにcountやsetCountのような名前は自由に設定できますが、セッター関数の名前は「set+ステート名」のように使われることが多いです。上の例で言えば、ステートがcountのときのセッター関数はsetCount、nameのときにはsetNameのような感じです。

useStateでカウンターを作ってみる

では、useStateを使って簡単なカウンターを作ってみましょう。
useStateを使うと次のようなコードで、クリックするとカウントアップするカウンターを作ることができます。

JS
コピーする
import React, { useState } from "react";
export default () => {
const [count, setCount] = useState(0);
return (
<div>
<p>ボタンを{count}回クリックしました</p>
<button onClick={() => setCount(count + 1)}>クリック!</button>
</div>
);
};

countがステートの値、setCountがセッター関数です。

ボタンをクリックするたびにcountの値が+1されていますね。


useEffect

useStateの次はuseEffectというReactフックについて見ていきましょう。

useEffectは(componentDidMountのような)Reactのライフサイクルメソッドを使わずに、関数コンポーネントのままライフサイクルメソッドと同じようなことができるフックです。
イメージ的には、useEffectは次の3つのライフサイクルメソッドがまとまったものになります。

useEffect

  1. componentDidMount
  2. componentDidUpdate
  3. componentWillUnmount

useEffectの使いどころ

useEffectはReactアプリケーションが読み込まれたときやコンポーネントのステートの値が変化したときなど、次のようにReactがレンダリングをした後に何か処理を加えたいときに便利です。

useEffectが役に立つときの例

  • 初期化のタイミング
  • タイマー
  • チェックボックスのOn/Off
  • コンポーネント内のDOMの操作
  • HTTPリクエスト

useEffectの基本的な使い方

useEffectの基本的な形は次のようになります。

JS
コピーする
import { useEffect } from 'react'
useEffect(() => {
// HTTPリクエストなどのサイドエフェクト
return () => {
// clean up 関数(componentWillUnmount に書くような処理)
};
}, []);

useEffectを使ってボタンをクリックするたびにtitleタグを更新してみる

useStateに比べると使い方が少し分かりにくいので、まずは実際にuseEffectを使ってみたいと思います。
ここではuseEffectを使って、ボタンをクリックしたタイミングでtitleタグの値を更新する機能を作ってみましょう。

JS
コピーする
import React, { useState, useEffect } from "react";
export default () => {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `ボタンを${count}回クリックしました`;
}, [count]);
return (
<div>
<p>ボタンを{count}回クリックしました</p>
<button onClick={() => setCount(count + 1)}>クリック!</button>
</div>
);
};

(分かりにくいかもしれませんが)クリックするたびにDOM(titleタグ)が更新されていますね。

これまでだったらDOMの操作をするときにはcomponentDidMountを使ってDOMが描画された後に処理を加えていましたが、useEffectを使えばクラスベースのcomponentDidMountを使わずに描画を待ってからDOMを操作できるようになります。

最初の読み込み時のみエフェクトを呼び出すには

最初の読み込みのみエフェクトを呼び出したいときは、第2引数(依存配列)の中身を空にしておきます。第2引数に空の配列を渡すことで他のレンダリング時に呼び出されないようになります。初期化するときに便利ですね。

JS
コピーする
// ○ 初期化のときにだけエフェクトを呼び出し(依存配列あり)
useEffect(() => {
console.log('読み込み完了')
}, []);
// × 毎回エフェクトが呼び出されてしまう(依存配列なし)
useEffect(() => {
console.log('毎回エフェクト呼び出し')
});

いつエフェクトを呼び出すか指定するには

上の例では最初の読み込みのみエフェクトを呼び出していましたが、それ以外のタイミングでもエフェクトを呼び出すことができます。

方法は簡単で、第2引数の依存配列の中にステートや関数を入れるだけです。
こうすることで、引数で渡した値が変更されたタイミングでエフェクトを呼び出すことができます。

JS
コピーする
// ○ countの値が変化したときにエフェクトが呼び出される(依存配列あり)
useEffect(() => {
document.title = `ボタンを${count}回クリックしました`;
}, [count]);
// × 毎回エフェクトが呼び出されてしまう(依存配列なし)
useEffect(() => {
document.title = `ボタンを${count}回クリックしました`;
});

上のコードのコメントにも書きましたが、もし第2引数に何も値を渡さないと更新のタイミングで毎回エフェクトが呼び出されてしまうので注意が必要です。

マウントを解除したときの処理を追加するには

useEffectでは、ライフサイクルメソッドのcomponentWillUnmountと同じようなマウント解除の処理を追加できます。
マウント解除の処理を追加するには、次のようにuseEffectの中でreturnを使います。こうすることで、マウント解除したときの処理を実行することができます。

JS
コピーする
useEffect(() => {
console.log('読み込み完了')
return () => {
console.log('マウント解除')
}
}, []);

マウントを解除したときの処理には、setIntervalをReactで使ったときにセットで使うclearIntervalなどを書いたりします。

JS
コピーする
import React, { useState, useEffect } from "react";
export default () => {
const [timer, setTimer] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setTimer((timer) => timer + 1);
}, 1000);
return () => {
clearInterval(interval);
};
}, [timer]);
return <p><strong>{timer}</strong> 秒経過しました</p>;
};

ただのタイマーなので動画にするほどでもないと思いますけど…上のコードを実行すると次のようになります。

useEffectが使えるようになるまではclearIntervalをcomponentWillUnmountを使うことが多かったと思いますが、useEffectが出てきたことでcomponentWillUnmountを使わなくても良くなりました。便利ですね。

useLayoutEffectよりuseEffectが推奨されている

ここまでuseEffectについて見てきましたが、useEffectと似たようなフックにuseLayoutEffectというReact公式フックもあります。

ただ、React公式サイトでも推奨しているように、useLayoutEffectではアプリケーションが遅くなる可能性があるので、画面がちらつくなどの問題がなければuseEffectを使う方が良さそうです。useLayoutEffectについてはこのページの下の方で紹介しているので、もしよかったらご確認ください。


useContext

Reactアプリケーションでステートを共有しようとすると、propsをバケツリレーするためにラッパー地獄に落ちてしまうことがありましたが、useContextを使えば地獄に落ちずにステートやデータを共有しやすくなります。

useContextの使いどころ

ひとつの場所でステートの管理をしたいけど、Reduxを使うほど規模の大きいプロジェクトではないときにも使われています。
アプリケーション全体で使いまわしたいテーマなどのスタイル関係や、認証機能の実装などに便利です。

ダークモードとライトモードを切り替える機能を実装してみる

ここではuseContextの使い方として、コンテクストを使ってダークモードとライトモードを切り替える機能を実装してみましょう。

① まずはcreateContext()

useContextフックでコンテクストにアクセスするには、createContextを使います。
crateContextはコンテクストオブジェクト(ここではthemes)を受け取り、その時点のコンテクスト(ThemeContext)を返してくれます。

JS
コピーする
import React from "react";
import { useState, useContext } from "react";
const themes = {
light: {
color: "#222",
backgroundColor: "#eee",
},
dark: {
color: "#ddd",
backgroundColor: "#333",
},
};
export const ThemeContext = React.createContext(themes.dark);
② AppコンポーネントをProviderで囲む

アプリケーション全体でthemesコンテクストを利用するために、ThemeContext.ProviderでAppコンポーネントを囲みます。
Material UIを使うときに<MuiThemeProvider theme={theme}>でAppコンポーネントで囲むのと同じような感じです。

valueにはオブジェクト形式でダークモード用の配色(themes.dark)を渡します。

JS
コピーする
import { ThemeContext } from './ThemeContext';
const App = () => {
return (
<ThemeContext.Provider value={themes.dark}>
// ...
</ThemeContext.Provider>
);
};
③ コンテクストを利用する

コンポーネントの中で作成したコンテクストを使うには、useContextThemeContext.Consumerを使います。

ThemeContext.ConsumerでもuseContextでも、どちらの方法を使っても大丈夫なのですが、ThemeContext.Consumerだとreturnブロックの中でしか使えないのと、個人的にはuseContextの方が使いやすいと思うので、ここではuseContextでデモ用のコンポーネントを作ってみます。

JS
コピーする
import { useContext } from "react";
import { ThemeContext } from './ThemeContext';
const LoremIpsum = () => {
const theme = useContext(ThemeContext);
return (
<div style={{ backgroundColor: theme.backgroundColor }}>
<p style={{ color: theme.color }}>
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Explicabo,
quis!
</p>
</div>
);
};

ちなみに先ほどのコードでvalue={themes.dark}と指定したので、themeオブジェクトにはダークモード用の配色がオブジェクト形式で入っている状態です。

④ ここまでのコードをまとめると

ここまでのコードをまとめると、下のようになります。
これでダークモードとライトモードを切り替える機能の完成です。

JS
コピーする
import React, { useState, useContext } from "react";
const themes = {
light: {
color: "#222",
backgroundColor: "#eee",
},
dark: {
color: "#ddd",
backgroundColor: "#333",
},
};
export const ThemeContext = React.createContext(themes.dark);
const LoremIpsum = () => {
const theme = useContext(ThemeContext);
return (
<p style={{ color: theme.color, backgroundColor: theme.backgroundColor, padding: '1rem' }}>
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Explicabo, quis!
</p>
);
};
const App = () => {
const [isDark, setDark] = useState(true);
const currentTheme = isDark ? "dark" : "light";
return (
<ThemeContext.Provider value={themes[currentTheme]}>
<LoremIpsum />
<button onClick={() => setDark(!isDark)}>テーマカラーを変更</button>
</ThemeContext.Provider>
);
};
export default App;

その他のReact公式フック

ここまで3つの基本的なReactフックを紹介してきましたが、Reactには上で紹介したフック以外にも次のような公式フックがあります。

その他のReact公式フック

  • useRef
  • useReducer
  • useMemo
  • useCallback
  • useLayoutEffect
  • useDebugValue

それぞれどのようなフックなのか見ていきましょう。

useRef

useRefはReact要素やrenderメソッドで生成されたDOMノードにアクセスできるフックです。
要素にフォーカスするときなど、実際のDOMへのアクセスが必要なときに役に立ちます。

JS
コピーする
const textInput = useRef(initialValue);

useRefの使用例

下のコードでは、ボタンをクリックしたときに入力フォームにフォーカスします。

JS
コピーする
import React, { useRef } from "react";
export default () => {
const textInput = useRef(null);
const focusInput = () => textInput.current.focus()
return (
<div className="App">
<input type="text" ref={textInput} />
<button onClick={focusInput}>Inputの内容を編集する</button>
</div>
);
};

createRefでもuseRefと同じような機能を実装できる

上ではuseRefというReactフックを使って書きましたが、createRefでも同じような機能を実装することができます。上のコードをcreateRefで書き直すと次のようになります。

JS
コピーする
import React, { createRef } from "react";
export default () => {
const textInput = createRef();
const focusInput = () => textInput.current.focus();
return (
<div className="App">
<input type="text" ref={textInput} />
<button onClick={focusInput}>Inputの内容を編集する</button>
</div>
);
}

useRefとcreateRefの違いは?

createRefではすべてのレンダリングで新しい参照を返しますが、useRefでは毎回同じ参照を返します。そのため、useRefではコンポーネントが再びレンダリングされても.currentに同じ値を持たせることができます。

※ createRefはuseRefより前のバージョン16.3から追加されました。


useMemo

useMemoは指定した関数をレンダリングごとに呼び出さないようにしてくれるフックです。
useMemoは最初のレンダリングで一度だけ計算を実行し、他のすべてのレンダリングではキャッシュされた値を返してくれるので、通常は計算量が膨大になる関数の最適化のために使われます。

ただ、気軽に使って逆にパフォーマンスを落としてしまう可能性もあるので、必要に迫られたら使うくらいの感覚で良いかもしれません。
雰囲気で使わない React hooks の useCallback/useMemo

useMemoの使い方

useMemoはuseEffectやuseCallbackと同じように、アロー関数の中で使いたい関数を呼び出します。もしuseMemoに渡す関数が引数を受け取る場合には、配列(依存配列)の中に使いたい引数を含めれば大丈夫です。

JS
コピーする
import React, { useMemo } from 'react';
const example = useMemo(() => expensiveCalculation(a), [a]);

useEffectと同じようにuseMemoの依存配列(引数)は空にしても大丈夫ですし、複数入れてもOKです。

JS
コピーする
const example = useMemo(() => expensiveCalculation(a, b), [a, b]);

また、React.memoを使うとライフサイクルメソッドのshouldComponentUpdateを実現できるようです。


useCallback

useCallbackは依存配列を渡すことで、必要のないレンダリングを防いでくれるReact公式フックです。

useCallbackの使い方

useCallbackはuseEffectuseMemoと同じように、インラインでコールバック関数を渡します。

JS
コピーする
import React, { useCallback } from 'react'
const example = useCallback(() => expensiveCalculation(a, b), [a, b]);
引数がなくてもuseCallbackには依存配列は必要

useCallbackはuseEffect同じように、依存配列を渡さないと呼び出しのたびに新しい値が返ってきてしまうので、コールバックで引数を使わなかったとしても空の配列を渡してあげます。

JS
コピーする
// ○(依存配列あり)
const example = useCallback(() => expensiveCalculation(), []);
// ×(依存配列なし:毎回新しい値が返ってきてしまう)
const example = useCallback(() => expensiveCalculation());

useMemoとuseCallbackの違い

useCallbackと似ているフックにuseMemoがありますが、useCallbackとuseMemoの違いは次のようになります。

useMemoとuseCallbackの違い

  • useMemoは値を返し、useCallbackはコールバック関数を返す
  • useCallbackは依存配列の要素が変更されるとレンダリング

useMemoは値を、useCallbackはコールバック関数を返す

useMemoとuseCallbackは目的や使い方など似ているところもありますが、useCallbackは値ではなくメモ化されたコールバック関数が返されます。
そのため、もし膨大な計算処理が必要なときには、useCallbackよりも計算結果(値)をキャッシュしてくれるuseMemoを使ったほうが効率が良さそうです。

useCallbackは依存配列の要素が変更されるとレンダリング

useCallbackもuseMemoも依存配列に要素を渡しますが、useCallbackはuseMemoと違って、依存配列の要素が変更されたタイミングで再レンダリングされてコールバック関数を返します。


useLayoutEffect

useLayoutEffectはほとんどの場合でuseEffectと同じようなReact公式フックです。
useEffectやuseLayoutEffectは次のようなライフサイクルメソッドに対応しています。

  • componentDidMount
  • componentDidUpdate
  • componentWillUnmount

useLayoutEffectとuseEffectはシグネチャーが同じなので、引数や返り値、例外、利用可能性の情報(publicやstaticなど)といった入力と出力が同じになります。

useEffectとuseLayoutEffectの違い

useEffectとuseLayoutEffectの違いとしては処理が実行されるタイミングになります。

useEffectの場合

useEffectの場合には、useEffectの実行を待たずに画面がレンダリングされます。

  1. コンポーネントをレンダリング
  2. 画面を更新
  3. useEffectを実行

useLayoutEffectの場合

useLayoutEffectの場合には、コールバック関数が完了するのを待ってからレンダリングされます。
useLayoutEffectはすべてのDOMの変更後に、同期的にコールバック関数が実行されます。

  1. コンポーネントをレンダリング
  2. useLayoutEffectを実行
  3. 画面を更新(ReactはuseLayoutEffectが終了するのを待ってから画面を更新)

useLayoutEffectの基本的な使い方

useLayoutEffectの基本的な使い方は次のようになります。

JS
コピーする
import React, { useLayoutEffect } from 'react'
useLayoutEffect(() => {
// HTTPリクエストなどのサイドエフェクト
return () => {
// clean up 関数(componentWillUnmount に書くような処理)
};
}, []);

画面のちらつきが気になる場合にはuseLayoutEffectを

上のようにuseLayoutEffectとuseEffectは処理のタイミングが違うので、もしuseEffectで画面のちらつきが起きてしまうような場合にuseLayoutEffectを試してみると収まるかもしれません。

useLayoutEffectの場合にはコールバック関数が完了するのを待ってからレンダリングされるので、処理の順番的にuseEffectの方が早めの段階で実行されます。公式サイトでも推奨しているように、まずはuseEffectを利用してみて、それでも問題が残る場合にはuseLayoutEffectを使うくらいの感じが良さそうですね。


useDebugValue

useDebugValueはカスタムフックをReact Developer Tools(Chrome拡張機能)に表示させることができます。

useDebugValueの基本的な使い方

useDebugValueはカスタムフックの中で表示したい値をuseDebugValueに渡します。

JS
コピーする
import React, { useDebugValue } from 'react';
useDebugValue(value);

さいごに

今回はReact Hooksについてまとめてみました。

フックによっては普段なかなか使わないものもあると思いますが、useStateuseEffectなどのフックは個人的にはかなり便利だと思います。もしまだフックをそこまで使っていないという方は、ぜひこれを機会に試してみてください。

それでは今回はこのあたりで。閲覧ありがとうございました。

Copyrights © WebCraftLogAll Rights Reserved.