React · LEARN AT YOUR OWN PACE今日も、一歩ずつ。

LESSON 08 · 約35分

Effectは、外部との同期に使う

必要性を判断してからEffectを書き、依存値とcleanupを対応させます。

コードを試す前に

01のVite reactテンプレート(JavaScript/JSX)を使います。特記のないApp.jsxは、そのレッスンだけで動く独立した例です。例同士を連結しないでください。

短い学習用の例です。各App.jsxは独立した例で、順に追記して一つの完成品にする形式ではありません。ファイル名、依存関係、未定義部分の前提は注記に記載します。問題内の抜粋や空欄は、そのまま実行するための完成コードではありません。

開発環境の準備を確認する →

このレッスンでわかること

  • イベント・表示の計算・外部同期を区別できる
  • 登録と解除が対になるEffectを書ける

最初に、Effectが必要か考える

ボタンから追加する処理はイベントへ、合計や絞り込みは描画中の計算へ置きます。Reactの外にある購読、タイマー、ブラウザーAPI、外部ウィジェットなどを現在のUIと合わせるときにEffectを検討します。stateが変わったという理由だけでEffectを作りません。

同期の開始と停止を対にする

この例はwindowのresizeイベントを購読し、表示中の部品へ画面幅を伝えます。Effectが登録し、返した関数が解除します。部品が消えるときだけでなく、依存値が変わって再実行する前にも古いcleanupが呼ばれます。開発時にStrict Modeが追加の開始・停止を行っても購読が増えない形にします。

依存配列は気分で選ばない

Effect内で読むpropsやstateなどのリアクティブな値は依存値です。外部接続先がroomIdなら、roomIdが変わると接続を切り替える必要があります。依存関係の警告を消して実行回数だけを減らすと古い値を使い続けます。下の幅の例は外部から値を読むだけで、Effect内でpropsやstateを読まないため空配列です。

  • []でも「どんな状況でも絶対に一度だけ」という保証ではない
  • Effectはクライアントで動く。サーバー描画中には実行されない
  • データ取得には競合や中断、読み込み・失敗の扱いも必要。フレームワークの取得機能も比較する

ブラウザーの画面幅を購読する

src/App.jsx
import { useEffect, useState } from 'react';

function WindowWidth() {
  const [width, setWidth] = useState(null);

  useEffect(() => {
    function handleResize() {
      setWidth(window.innerWidth);
    }
    handleResize();
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return <p>画面幅: {width === null ? '計測中' : `${width}px`}</p>;
}

export default function App() {
  const [visible, setVisible] = useState(true);
  return (
    <main>
      <h1>外部との同期</h1>
      <button type="button" onClick={() => setVisible(value => !value)}>
        {visible ? '計測を隠す' : '計測を表示'}
      </button>
      {visible && <WindowWidth />}
    </main>
  );
}

windowをEffect内で読みます。非表示ではWindowWidthがアンマウントされ、その購読が解除されます。

依存値でタブのタイトルを同期する

src/PageTitle.jsx
import { useEffect } from 'react';

export default function PageTitle({ title = 'Reactの練習' }) {
  useEffect(() => {
    const previousTitle = document.title;
    document.title = title;
    return () => { document.title = previousTitle; };
  }, [title]);

  return <h1>{title}</h1>;
}

App.jsxとは独立した部品例です。利用する親からtitleを渡します。タイトルを管理する部品がこの1つだけの構成を前提にし、Next.jsではMetadata APIなどを優先します。

TRY IT YOURSELF

購読の開始と解除を確かめる

  1. ブラウザーの幅を変更して数値が追従するか確かめる
  2. handleResize内へ一時的にconsole.logを置き、計測を隠した後のresizeで出力しないか確認する
  3. 表示・非表示を数回繰り返し、登録が重複しないことを確かめてログを外す
  4. クリック保存・絞り込み・画面幅購読の3つを、イベント/計算/Effectに分類する

できたらOK:表示中だけ購読が働き、非表示後は解除され、各処理をEffectへ置く理由を説明できる

QUICK CHECK

理解を確かめよう

useEffectを使う理由として適切なのはどれ?

公式資料でもう少し詳しく

ここまで読めたら、ひとつ前進。