これまでのReactは、アプリケーションのロジックに関わるコンポーネントをクラスコンポーネントで記述していました。

そのため複雑になりがちなコードになっていたのが、React Hooksが誕生したことによって色々なメリットが生まれました。

それではみていきましょう。

React Hooksとは

stateやライフサイクルを関数コンポーネントでも使える機能です。

React 16.8から追加された新機能で、100%後方互換により古いコードの書き方をしていても、独立してReact Hooksを導入することができます。

hookを使うメリット

クラスコンポーネントの場合、stateを扱うロジックが複雑になったり、ライフサイクルメソッドは時間軸で処理の記述箇所を変えるので、副作用のある処理がまたがってしまいます。

hookを使うことで処理をまとめることができ、クラスコンポーネントで書くよりシンプルさを保つことができます。

それではhookを見ていきましょう。

useState()

ステートフックと呼ばれ、statesetState()を代替します。

複数のstateを扱うときはstate毎にuseState()宣言します。

useStateの使い方

【useStateフックをインポート】

import React, { useState } from 'react';

useStateフックをインポートすることで、関数コンポーネント内でローカルstateが使えるようになります。

【宣言】

const [state変数名, state更新関数名] = useState(state初期値)

[ ]内の記述方法はJavaScriptの分割代入を使っています。

useStateから返される値の1つめをstate変数名に、2つめをstate変更関数名に返しています。

var nameStateVariable = useState(state初期値); // 配列で返ってくる
var name = nameStateVariable[0];  // 現在のstateの値
var setName = nameStateVariable[1]; // stateを更新するための関数

分割代入を使わずに記述すると以上のようになります。

分割代入の方が書きやすく、まとまって直感的に見やすいですよね。

【stateの読み出し】

{ state変数名 }

クラスコンポーネントのようにthis.stateが必要なく{ }内にstate変数名だけを記述するだけで読み出せます。

【stateの更新】

state変更関数名(変更処理)

setState()で更新はせず、useState()で宣言したstate変更関数を実行して、stateを更新します。

【useState()使用例】

【コード】


import React, {useState} from 'react';

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

  return (
    <div>
      <p>クリック数:{count}回</p>
      <button onClick={() => setCount(count + 1)}>クリック</button>
    </div>
  );
}

export default App;
  1. 2行目でuseStateフックをインポートし、ローカルstateを使えるようにします。
  2. 5行目でcountというstate変数にuseState()で初期値を0に設定します。
  3. 10行目でボタンをクリックした時に、setCount関数が実行され、count変数を1増やす処理をします。
  4. 9行目でクリック数:{count}回のcount変数のstateが更新され、クリックするたびに1ずつ増えるようになります。

useEffect()

ライフサイクルメソッドの代替として、関数コンポーネントで利用できます。

機能ベースにコードがまとめられるので、時間軸に記述するライフサイクルメソッドよりシンプルに記述できます。

useEffectの仕組み

レンダーごとにuseEffect内の処理が実行されます。

クラスコンポーネントでいう

  • componentDidMout()
  • componentDidUpdate()
  • componentWillUnmount()

のメソッドの代替ができます。

他のライフサイクルメソッドには対応していませんが、パフォーマンスのチューニングなどに使われるのものなので、アプリケーションを作る上で最低限必要なメソッドは揃っています。

useEffectは1つのhookでこれらライフサイクルメソッドを代替するので、記述方法にいくつかパターンがあり、それによって再現しています。

それではそのパターンをみていきましょう。

1.レンダー毎

useEffect(() => {
  // コンポーネントがレンダーされる時(再レンダー含む)に実行される。
  return () => {
    // コンポーネントが破棄される時に実行される。
  }
})

useEffect()内にコールバック関数を記述します。

コールバックはレンダー毎に呼ばれて、returnするコールバック関数はコンポーネントが破棄される時に呼ばれます。

returnする時のコールバック関数をクリーンアップ関数と言います。

つまり最初のコールバック関数がcomponentDidMout()componentDidUpdate()の役割をになっていて、returnするコールバック関数はcomponentWillUnmount()の役割を担っていることになります。

2.マウント時

useEffect(() => {
  // コンポーネントがレンダーされる時に実行される。
}, [])

クラスコンポーネントでいうcomponentDidMout()にのみ処理を実行するパターンです。

第二引数に空の配列を渡してあげ、returnをしません。

useEffectは第二引数の配列内の値を、前回のレンダーと今回のレンダーで毎回比較をして、変更があった場合のみコールバック関数が実行される仕組みになっています。

つまり空の配列を渡すことによって、最初の1回(マウント時)のみ実行されることになります。

3.マウント・アンマウント時

useEffect(() => {
  // コンポーネントがレンダーされる時に実行される。
  return () => {
    // コンポーネントが破棄される時に実行される。
  }
}, [])

パターン1と2の複合形で、クラスコンポーネントでいうcomponentDidMout()componentWillUnmount()の処理を実行するパターンです。

4.特定のレンダー時

useEffect(() => {
  // コンポーネントが最初の1回と、特定の値が変更されてレンダーされる時に実行される。
}, [変更値])

マウント時に実行され、さらに第二引数の配列の変更値が変わった時に実行されます。

パターン4の実行例

const [name, setName] = useState('Taro');
useEffect(() => {
  // コンポーネントが最初の1回と、特定の値が変更されてレンダーされる時に実行される。
}, [name])

nameの「Taro」が変更されたされた時に実行される。

useEffectの使い方

【useEffect()使用例】

【コード】


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

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

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

  useEffect(() => {
    document.getElementById('counter').addEventListener('click', countUp);
    if(count >= 10) {
      setCount(0);
    }

    return() => {
      document.getElementById('counter').removeEventListener('click', countUp);
    }
  }, [limit]);

  return (
    <div>
      <p>クリック数:{count}回</p>
      <button id={"counter"}>カウント</button>
      <button onClick={() => setLimit(!limit)}>解除</button>
    </div>
  );
}

export default App
```
  • 2行目でuseEffectをインポートします。
  • 13行目のカウントボタンは一回実行すると、limitの値が変更されるまで実行できません。
  • 20行目で解除ボタンでlimitの値が切り替わるので、13行目のカウントボタンが実行できるようになります。
  • 解除ボタンをクリックした時、19行目が実行されます。

hookを使う時の注意点

トップレベルでしか呼び出せない

フックはメソッド内の一番外側でしか呼び出すことはできません。

【エラー例】


import React, {useState} from 'react';

const App = () => {
  const user = () => {
    const [name, setName] = useState('Taro');
    setName('Hanako');
  }

  return <div onClick={user}>{name}</div>
}

export default App;

メソッド内の一番外側でないuser()useName('Taro')を呼び出そうするとエラーになります。

【正常例】


import React, {useState} from 'react';

const App = () => {
  const [name, setName] = useState('Taro');

  const user = () => {
    setName('Hanako');
  }

  return <div onClick={user}>{name}</div>
}

export default App;

App()直下でhookを呼び出します。

無限ループで気を付ける

【エラー例】


import React, {useState} from 'react';

const App = () => {
  const [name, setName] = useState('Taro');
  setName('Hanako');

  return <div>{name}</div>
}

export default App;

setName('Hanako')→render→setName('Hanako')→render...のように無限ループになってしまいエラーが発生します。

【対応例】


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

const App = () => {
  const [name, setName] = useState('Taro');

  useEffect(() => {
    setName('Hanako');
  }, []);

  return <div>{name}</div>
}

export default App;

無限ループさせないために、setName('Hanako')の呼び出し回数を制限します。

まとめ

  • hookはstateやライフサイクルを関数コンポーネントでも使えるようになる機能で、100%後方互換性があり、独立して導入することができる。
  • useState()はstateを代替するhook。
  • useEffect()はライフサイクルを代替するhook。
  • useEffect()で代替できるライフサイクルはクラスコンポーネントでいうcomponentDidMout()componentDidUpdate()componentWillUnmount()になる。
  • hookはトップレベルで呼び出す。
  • 無限ループに気を付ける。

さいごに

以上、hookについてでした。

hookを使えるようになったことで、記述がシンプルになり保守性も良いコードが書きやすくなります。

今回紹介していないhookもありますが

ただhookがあるからクラスコンポーネントが必要ないということではないので、適材適所に使い分けて開発をしていくことが大事になるとか思います。

参考