本文書では、ReactのHookの中でもuseStateについで重要なuseEffectの使用方法について説明を行なっていきます。本文書を読み終えるとuseEffectの基本的な使用方法とライフサイクルとの関連をシンプルなコードを利用して理解することができます。

useEffectの動作確認のためにReack HookのuseStateを利用するのでuseStateの知識が必要になります。

React useEffectとは

useEffectはReact classのライフサイクルcomponentDidMount, componentDidUpdateとcomponentWillUnmountの3つと同様な処理を行うことができるHookです。useEffectを利用することでコンポーネントをレンダリングする際に外部のサーバからAPIを経由してデータを取得したり、コンポーネントが更新する度に別の処理を実行することができます。

ライフサイクルって何?という人でもわかるようにシンプルな例を使ってuseEffectの使用方法を確認していきます。

準備

useEffectの動作確認を行うためにそのベースになるコードを作成します。

useStateを使ってcountのstate変数を設定しボタンをクリックする度にCount数が増えるコードを作成します。


import React,{ useState} from 'react';
import './App.css';

function App() {

  const [count, setCount] = useState(0)

  return (
    <div className="App">
      <h1>Learn useEffect</h1>
      <h2>Count: { count }</h2>
      <button onClick={() => setCount(count+1)}>+</button>
    </div>
  );
}

export default App;

作成後、ブラウザを開くとCountの初期値は0ですが表示されている+ボタンをクリックするとCount数が増えることを確認してください。

Count数が増えることを確認
Count数が増えることを確認

useEffectの動作確認

この時点でuseEffectがどのようなものかわからなくても安心してください。ここから使い方を説明していきます。

初めてのuseEffect

useEffectを利用してコンソールログに文字列を表示するように先程のコードの更新を行います。useEffectを利用する場合は、useStateと同様にuseEffectをimportする必要があります。


import React,{ useState, useEffect} from 'react';
import './App.css';

function App() {

  const [count, setCount] = useState(0)

  useEffect(()=>{
    console.log('useEffectが実行されました')
  })

  return (
    <div className="App">
      <h1>Learn useEffect</h1>
      <h2>Count: { count }</h2>
      <button onClick={() => setCount(count+1)}>+</button>
    </div>
  );
}

export default App;

useEffectの中にconsole.logを追加するだけでブラウザをリロードするとコンソールログに以下のメッセージが表示されることが確認できます。

useEffectによるメッセージ
useEffectによるメッセージ
この時点でuseEffectはコンポーネントが表示される流れの中で自動で実行されることがわかります。

次に+ボタンを3回クリックしてください。ブラウザ上ではCountの数が3になり、コンソールログを見ると”useEffectが実行されました”のメッセージが4になっていることが確認できます。

コンソールログの確認
コンソールログの確認

ここまでの動作確認で、useEffectはブラウザでコンポーネントが初めて表示される時に必ず一度実行されること、Countの更新によりコンポーネントが更新される度に実行されることがわかりました。

useEffectが必ず実行されるので、外部からAPIで取得したデータを表示させたい場合に利用することができることが理解できます。表示する方法については後ほど実際のコードを使って説明します。

最初の1回の表示直後に行われるuseEffectの実行がライフサイクルのcomponentDidMountに対応し、それ移行のuseEffectの実行はcomponentDidUpdateに対応します。

コンポーネントの更新によるuseEffectの停止

コンポーネントが更新される度にuseEffectが実行されることがわかりました。しかし、コンポーネントの更新の度にuseEffectの中の処理が必要ではない場合もあります。そのような場合は、コンポーネントの更新によるuseEffectを止めることが可能です。その場合はuseEffectの第2引数に[]を追加します。


useEffect(()=>{
  console.log('useEffectが実行されました')
},[])

[]を追加後にブラウザを使って+ボタンをクリックしてCount数を増やしてください。コンポーネントが表示される最初の一回のuseEffectは実行されますが、ボタンをクリックしてコンポーネントを更新してもuseEffectは実行されなくなりました。

useEffectによるメッセージ
useEffectによるメッセージ

state変数によるuseEffectの実行

useEffectに空の配列を設定することでコンポーネントの更新によるuseEffectの実行を止めることが確認できました。しかし場合によってはある特定のstate変数のみの更新だけuseEffectを実行したいという場合があるかもしれません。その場合は追加した空の配列にstate変数を追加することだけでその変数の変化のみを監視してuseEffectを実行させることができます。

動作確認を行うために新たにstate変数count2を追加します。useEffectの配列にはcountだけを追加しています。つまりcountだけを監視し、この変数が更新されるとuseEffectが実行されます。


import React,{ useState, useEffect} from 'react';
import './App.css';

function App() {

  const [count, setCount] = useState(0)
  const [count2, setCount2] = useState(0)

  useEffect(()=>{
    console.log('useEffectが実行されました')
  },[count])

  return (
    <div className="App">
      <h1>Learn useEffect</h1>
      <h2>Count: { count }/ Count2: { count2 }</h2>
      <button onClick={() => setCount(count+1)}>Count+</button><br/>
      <button onClick={() => setCount2(count2+1)}>Count2+</button><br/>
    </div>
  );
}

export default App;

ブラウザを開いてCount+ボタンを3回、Count2+ボタンを5回クリックします。

追加したCount2
追加したCount2

Countボタンを押す時のみuseEffectが実行されるのでコンソールログには4回のメッセージが表示されます。

count更新によるメッセージ回数
count更新によるメッセージ回数
最初の1回はコンポーネントが表示される際のメッセージのため4回となります。

useEffectの[]配列の利用方法を理解することができました。

useEffect内でcount数を増やした場合の動作

useEffect内でcount数を増やすとどのような動作になるのか確認しておきましょう。


useEffect(()=>{
  console.log('useEffectが実行されました')
  setCount(count+1)
  setCount2(count2+1)
},[count])

countを配列に追加しておいてもどちらのstate変数も増加し続けることがわかります。これはcountというブラウザ上で表示させるものを設定させているので値が更新されるため動作し続けていることがわかりますが、ブラウザ上では表示されないFetchなどの処理がある場合には注意が必要です。

useEffect内でcountを増やす
useEffect内でcountを増やす

useEffectでの外部からデータをfetchする方法

useEffectはコンポーネントのマウント後に外部からデータを取得する際に利用することができるで、実際に外部のサーバを利用してどのように設定を行うか確認をしておきます。

外部のリソースは、JSONPLACEHOLDERを利用してfetchメソッドでpostsデータを取得します。

useEffectを利用する場合は第2引数に[](配列)をつけるのを忘れないでください。配列をつけていない場合はコンポーネントの更新によるuseEffectが実行され、サーバへのFetchが継続して行われることになりサーバへの負荷をかけることになります。

import React,{ useState, useEffect} from 'react';
import './App.css';

function App() {

  const [posts, setPosts] = useState([]);

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/posts')
      .then(response => response.json())
      .then(data => {
        setPosts(data)
      },[])
  })

  return (
    <div className="App">
      <h1>Learn useEffect</h1>
      <div>
        {
          posts.map(post => (
            <div key={post.id}>{post.title}</div>
          ))
        }
      </div>
    </div>
  );
}

export default App;

useStateを使ってstata変数postsを定義し、初期値を空の配列に設定します。マウント後にuseEffect後内のfetchメソッドが実行され、JSONPLACEHOLDERのURLにアクセスを行いpostsデータ一覧を取得します。取得したデータはsetPostでposts変数に挿入します。最後にmapメソッドで展開し、ブラウザに一覧を表示しています。

useEffectを利用すると簡単に外部からデータを取得し表示させることができます。

postのリスト一覧
postのリスト一覧

コンポーネントのアンマウント時の処理

ここまでの動作確認で、useEffectではライフサイクルのcomponentDidMount, componentDidUpdateと同様の処理が行えることが確認できました。

最後にcomponentWillUnmountと同様の処理について説明を行なっていきます。

新しいコンポーネントCount.jsの作成

コンポーネントのアンマウント時に行う処理の動作確認を行うために新たにCountコンポーネントを追加します。ファイルはcomponentsディレクトリの下にCount.jsファイルを作成します。

中身はuseStateでstate変数countを追加し、useEffectでコンポーネントのマウント後にsetIntervalを使って1秒ごとにcountを1つアップするといったものです。


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

function Count() {

    const [count, setCount] = useState(0)

    useEffect(() => {
        console.log('useEffectが実行されました')

        setInterval(() => {
            setCount(count => count + 1);
            console.log('カウントが1アップしました')
          }, 1000);

    },[])

    return (
      <div>
          <h1>Count: {count}</h1>
      </div>
    );
  }
  
  export default Count;

メインのApp.jsファイルからCountコンポーネントをimportして追加します。


import React,{ useState } from 'react';
import './App.css';
import Count from './components/Count.js'

function App() {

  return (
    <div className="App">
      <h1>Learn useEffect</h1>
      <Count/>
  );
}

export default App;

ブラウザで確認するとSetIntervalを設定しているので、1秒ごとにCountが1アップします。

Countが1秒ごとにアップ
Countが1秒ごとにアップ

useEffectは一度だけ実行され、コンソールログにはCountが増える度に”カウントが1アップしました”が表示されます。

コンソールログの表示
コンソールログの表示

アンマウント処理の追加

Countコンポーネントをアンマウントする際の処理を追加します。useEffectの中にreturnを追加し処理を設定することでアンマウント時にその処理が行われます。ここではconsole.logでメッセージを表示させます。


useEffect(() => {
    console.log('useEffectが実行されました')

    setInterval(() => {
        setCount(count => count + 1);
        console.log('カウントが1アップしました')
        }, 1000);

    return () => {
        console.log('コンポーネントがアンマウントしました')
    }
},[])

ここまでの設定では、ブラウザをリロードしてもアンマウント処理時のメッセージを表示させることはできません。アンマウントの処理を確認するために親側のApp.jsを利用して、Countコンポーネントの表示/非表示を制御します。

Toggle処理の追加

Countコンポーネントの表示/非表示を切り替えるためにApp.jsファイルにuseStateでstate変数displayを追加します。またToggleボタンを追加し、onClickイベントでdisplay変数の値を切り替えます。


import React,{ useState } from 'react';
import './App.css';
import Count from './components/Count.js'

function App() {

  const [display, setDisplay] = useState(true)

  return (
    <div className="App">
      <h1>Learn useEffect</h1>
      <button onClick={()=>setDisplay(!display)}>Toggle</button>
      {display && <Count/>}
    </div>
  );
}

export default App;

Countコンポーネントをdisplayがtrue, falseで表示・非表示に切り替えるために下記を追加しています。


{ display && <Count/> }

ブラウザで確認するとToggleボタンが表示され、1秒ごとにCountがアップします。

Toggleボタンが表示された状態
Toggleボタンが表示された状態

ToggleボタンをクリックするとCountコンポーネントが非表示になります。

Toggleボタンをクリック後
Toggleボタンをクリック後

コンソールを確認するとコンポーネントが非表示になり、アンマウントされたので、”コンポーネントがアンマウントしました”のメッセージが表示されますが、Warningが発生し、memory leakについて記述されています。またコンポーネントが消えたのにも関わらずカウントアップのメッセージは継続して表示されていることが確認できます。

コンソールログにエラー
コンソールログにエラー

クリーンアップ処理の追加

この問題を解決するためにアンマウント処理の中でsetIntervalをクリーンアップする処理を追加します。


useEffect(() => {
    console.log('useEffectが実行されました')

    const interval = setInterval(() => {
        setCount(count => count + 1)
        console.log('カウントが1アップしました')
        }, 1000)

    return () => {
        clearInterval(interval)
        console.log('コンポーネントがアンマウントしました')
    }
},[])

setIntervalをinterval変数に設定し、returnの中でclearIntervalを実行しています。

再度ブラウザでToggleボタンを押してCountコンポーネントを非表示にすると先程のwarningメッセージも表示されず、アンマウントしたメッセージが表示されます。

アンマウントメッセージ
アンマウントメッセージ

このようにuseEffectではマウント時に実行した処理をアンマウント時に解除する処理が必要となることを覚えていてください。

シンプルなコードを通して、useEffectとcomponentDidMount, componentDidUpdateとcomponentWillUnmountの関係を理解することができました。