INDEX
開く ▼

React学習:Reactフック編|useEffectとは?useLayoutEffectとの違いは?APIリファレンスを参考に徹底理解したい

Learning React Hook Useeffect Gkhacffagv

Reactのなんとなく理解を(勝手に)禁止するシリーズでブログを書いています!

今回は、useEffectを分かったつもりにならずに、改めてAPIリファレンスを見返しながら徹底理解していきます。下記のような不安のある人に、少しでも参考になるかもです。

ESLintのWarning

Warning: React Hook useEffect has missing dependencies: '*****' and '*****'. Either include them or remove the dependency array. react-hooks/exhaustive-deps

useEffectとは外部システムと同期させるためのReactフック

そもそもuseEffectは避難ハッチの一つで、Reactのシステムの範囲外の外部システムと同期するために使うものです。

避難ハッチ(Escape Hatches)とは|Reactドキュメント

データフェッチをしたり、ブラウザから提供されるオブジェクト(document等)やWeb APIを活用したり、そういったReactのシステムから踏み出した外部システムと同期する際に使うためのReactフック。

もしそのコンポーネントがReactのサイクル内で済むならuseEffectは必要ない。」と公式のドキュメントにも説明がある通り、あくまでもReactのシステム外を見に行く際に活用するのがuseEffect

また、useEffectはクライアント上でのみ動作するもので、サーバレンダリング中には実行されません。この辺りの注意点については、公式リファレンスを参照すると良いです。

useEffect - 注意点|React API Reference

useEffectの使用例を見ながら基本を理解する

公式APIリファレンスの例を参照しながら、下記のポイントをまず理解していきます。

外部システムと接続する一般的な使用例

import { useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

  useEffect(() => {
    /**
     * セットアップコード
     */
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    // ---------------
  	return () => {
      /**
       * クリーンアップコード
       */
      connection.disconnect();
  	};
    /**
     * 依存値リスト
     */
  }, [serverUrl, roomId]);
  // ...
}

上記のソースは、./chat.jscreateConnectionの関数にサーバーのURLとチャットルームIDを渡して、チャットサーバーに接続をしている例になります。まずはソースにもコメントしている「セットアップコード」と「クリーンアップコード」について説明していきます。

セットアップコード|クリーンアップコード

セットアップコードはuseEffectが呼び出される際に実行される処理。クリーンアップコードは、コンポーネントがアンマウントされた際や再びuseEffectが実行される際の後始末担当のようなもので、useEffectの第1引数の戻り値として設定します。

例えば上記の例では、チャット接続をするコンポーネントがアンマウントされた際は、その接続を切断する必要があります。そのような処理を、useEffectの第1引数の戻り値として設定してやることで実装できます。

依存値リスト(依存配列)

依存値リストは、useEffectの処理によって使用される全てのリアクティブな値を含む配列です。依存配列に詰め込んだ値が変更されると、セットアップコードとクリーンアップコードが決められたサイクルに従って再実行されます。

一番の注意点は「依存配列は自分で選ぶ類いのものじゃない」ということ。依存配列に何を含めるかは、useEffectのソースによって絶対的に決定されます。

例えば、上記のチャット接続の例で言えば、[serverUrl, roomId]でなければいけません。もう一度ソースを見てみます。

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
  	return () => {
      connection.disconnect();
  	};
  }, [serverUrl, roomId]);
  // ...
}

serverUrlroomIdはこのコンポーネント内に含まれたリアクティブな値(データの変更を検知して自動的に更新される値)です。serverUrlsetServerUrlによって変更される可能性があるし、roomIdChatRoom()が実行されたタイミングで変更される可能性があるので、どちらもリアクティブな値と言えます。

それらのリアクティブな値を依存配列に入れないと、予期しないバグを生む可能性がある(バグの例)ので、依存配列はuseEffectのソースによって絶対的に決められるものとなります。

リアクティブな値には、props と、コンポーネント内に直接宣言されたすべての変数および関数が含まれます。roomId と serverUrl はリアクティブな値であるため、依存値のリストから削除することはできません。それらを省略しようとした場合、React 用のリンタが正しく設定されていれば、リンタはこれを修正が必要な誤りであると指摘します。
useEffect - リアクティブな依存配列の指定|React API Reference

もし、依存配列に何も含めたくない(マウント時に一回だけ実行させたい)場合は、それをソース上で証明する必要があります。つまり、serverUrlroomIdはリアクティブな値ではなく、これらを検知しなくて良いことを下記のように示します。

/**
 * リアクティな値でないことの証明
 */
const serverUrl = 'https://localhost:1234'; // Not a reactive value anymore
const roomId = 'music'; // Not a reactive value anymore

function ChatRoom() {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
  	return () => {
      connection.disconnect();
  	};
  }, []);
  // ...
}

ChatRoom関数の外に逃したり更新されない定数として扱うことで、serverUrlroomIdに依存する必要がなくなって、依存配列を空にすることができます。こんな形で、依存配列を空にしたいならソースを変えるべきで、無理やり空にしては本当はいけないんです。やっぱり公式ドキュメントは勉強になる...

依存配列を不適切に空にした時のESLintのWarningについて

ちなみに、チャット接続の例でリアクティブな値があるのにも関わらず、依存配列を設定しないと下記のようなWarningが出ると思います。

Warning: React Hook useEffect has missing dependencies: '*****' and '*****'. Either include them or remove the dependency array. react-hooks/exhaustive-deps

この時に下記のようにESLintを無理やり黙らせるやり方が記事とかでも出回っていると思いますが、やめた方が良いです。

useEffect(() => {
  // ...
  // 🔴 Avoid suppressing the linter like this:
  // eslint-ignore-next-line react-hooks/exhaustive-deps
}, []);

これについては公式のセリフをそのまま載せておきます。

依存配列がコードと一致しない場合、バグが発生するリスクが高くなります。リンタを抑制することで、エフェクトが依存する値について React に「嘘」をつくことになります。代わりにそれらが不要であることを証明してください。

前述の通り、「依存配列は自分で選ぶ類いのものじゃない」ので、空を想定しているなら空で良いように(ESLintのWarningが出ないように)証明する必要があります。依存配列を取り除く例については、下記のドキュメントに詳細に書かれているので参考になります。

エフェクトから依存値を取り除く

useEffectのセットアップコードとクリーンアップコードのサイクル

依存配列が空の時のuseEffectサイクル

learning-react-hook-useeffect-001@1.5x.jpg

依存配列に何も設定されていなければ、useEffectのセットアップコードはコンポーネントがマウントされた後の1回のみに実行され、アンマウント時にクリーンアップが走ります。

依存配列が設定されている時のuseEffectサイクル

learning-react-hook-useeffect-002@1.5x.jpg

useEffectがリアクティブな値に依存していれば、その依存値が更新されたタイミングで ①クリーンアップ、②セットアップ の順で実行されます。クリーンアップはセットアップの処理をお掃除する役割なので、マウント後に実行された処理を先にお掃除する必要があるため、クリーンアップが先に実行されます。

useLayoutEffectとは?useEffectとの違いとは

useLayoutEffectは、Reactの「トリガー・レンダー・コミット」のプロセスの内、ブラウザがコミットを受け取って画面上に描画する前に「同期的に」実行されます。

そもそもuseEffectはブラウザ上に変更結果が描画される処理とは別軸で非同期的に実行されます。それぞれ下記の違いとメリットがあります。

useEffect useLayoutEffect
ブラウザの描画とは別に
非同期的に実行
ブラウザの描画前に
同期的に実行
ブラウザの描画と連携してUI調整を
したい時にちらつくことがある
ブラウザの描画前に実行されるので
パフォーマンスを下げる恐れがある

useEffectが問題となるとき...例えばちらつき問題とは、下記のような現象です。

img-tech-blog-f3c4creot7dvgx6hra5p.gif

ボタンを押す度にランダムな数値を更新しているのですが、初期値がチラついて見えてしまっています。(目立つように悪魔の数字666👿にしましたが、下記ソースでは0を初期値にしてます)

function App() {
  const [state, setState] = useState(0)
  
  useEffect(() => {
    console.log('setup')
    state === 0 && setState(Math.random())
  }, [state])

  return <>
    <button onClick={() => {
      console.log('clickEvent')
      setState(0)
    }}>{state}</button>
  </>
}

ボタンがクリックされる度にstateが更新され、useEffectが発火します。初期値が0なのですが、useEffectでランダム数値に変更されて、ここらの処理が非同期的に行われることでチラついて見えてしまいます。

上記をuseLayoutEffectに変えてやればブラウザに描画されるよりも前にuseEffectという副作用処理を終えて、同期的にブラウザの描画に移行するので綺麗にランダム数値だけが表示されます。

useLayoutEffectのサイクルを見てみればより分かりやすいかもしれません。

useLayoutEffectのサイクル

learning-react-hook-useeffect-003@1.5x.jpg

useLayoutEffectの場合は、Updatedeと書いている箇所(公式的にはReactのrenderフェーズ)を全てやり切ってから、ブラウザ上に反映させるcomponentDidUpdateの処理に移ります。なので、useEffectによる余計なブラウザ描画が行われない訳です。

僕の出した例はチラつきを分かりやすくしたソースで正直そんな書き方をしなければ解決しますが...もっと現実味のある具体例は下記の公式APIリファレンスを参照してみてください。(ちょっと重めだけど...)

useLayoutEffect|React API Reference

References

スキルアップ転職を目指すなら「エンジニア特化型転職エージェント」を活用しよう

もし下記のような悩みがあれば、スキルアップを目的とした転職をするのもひとつの手段です。

・モダンな環境での経験が積みたい...
・実際に働いてみてもっと取り組みたい案件が明確になった
・学習をしているが先が見えずこのまま1人でスキルアップするのは辛い...
・今の職場ではなかなか理想の案件にアサインしてもらえない...

レバテックキャリアやユニゾンキャリアといったエンジニアに特化した転職エージェントであれば、初回面談でまずは現状の悩みを聞いてくれたり丁寧にヒアリングをしてくれます。

さらに、プロのキャリアアドバイザーが企業個別の技術質問・頻出質問の対策、模擬面接でのサポート、ES対策、企業分析・自己分析、求人の紹介など、あなたの転職を徹底サポートしてくれて...下記のどの状況の人でもメリットがあるので、まだ使っていない人は無料相談から進めておきましょう。

シチュエーション メリット
既に採用面接を控えている人 模擬面接や面接企業の分析をしてくれる
応募先企業の固有の
質問を気にしている人
応募先企業の頻出質問や気にしていることなど、企業ごとの対策をしてくれる
応募企業がまだ少ない人 応募したい企業をヒアリングしてくれ、ベストな求人をピックアップしてくれる
転職に自信がない人 転職理由や希望条件をヒアリングの上、ベストな応募企業や採用面接のコツをレクチャーしてくれる

IT・Web業界のエンジニア転職に強いエージェントがおすすめ。筆者や同僚も実際に活用していたエージェントをまとめておきます。

現役がおすすめするIT・Webエンジニア特化型の転職エージェント

IT・WEB・ソーシャルゲーム業界への転職ならGEEKLY
The Service Aid Your Career.
ボタンをクリックして無料の転職相談から始めよう!
IT・Web業界での転職なら【レバテックキャリア】
The Service Aid Your Career.
ボタンをクリックして無料の転職相談から始めよう!
IT・Web・ゲーム業界特化の転職エージェント【ユニゾンキャリア】
The Service Aid Your Career.
ボタンをクリックして無料の転職相談から始めよう!
ITエンジニア就職に特化した【ウズカレIT】
The Service Aid Your Career.
未経験ITエンジニアの就職サポートに特化したサービスに無料相談しよう!