Part 6 · 副作用 (useEffect)
useEffect の役割
React の外側と同期をとるための道具
useEffect は、React のフックの中でもっとも誤用されているものです。
「何かのタイミングで処理を走らせるもの」と覚えてしまうと、 あらゆる場面で使いたくなり、そのたびにバグを増やします。
正しくは、もっと狭い道具です。React の外側にあるものと、React の状態を合わせるためのもの。 それ以外の用途では、まず使いません。
「React の外側」とは
React が面倒を見てくれるのは、state から画面が作られるところまでです。 それ以外は React の管轄外です。
- ブラウザのタブ名やアドレス欄
- タイマー(setInterval など)
- ブラウザのイベント(スクロール、リサイズ)
- サーバーとの通信
- localStorage などの保存領域
- React で書かれていない他のライブラリ
これらと state を合わせたいときに使うのが useEffect です。
押すとブラウザのタブ名が変わる
タブ名は React が管理していません。 JSX に書いて変えられるものではないので、こちらから外へ働きかける必要があります。 これが useEffect の出番です。
title-sync.tsx
"use client";
import { useTrackDemoRender } from "@/components/lesson/demo-card";
import { Button } from "@/components/ui/button";
import { useEffect, useState } from "react";
export function TitleSync() {
// このカードが描き直された回数を数えるための 1 行(教材の仕掛け)
useTrackDemoRender();
const [count, setCount] = useState(0);
useEffect(() => {
// ブラウザのタブ名は React の管理外。ここで直接書き換える
document.title = `${count} 回押されました`;
// このページを離れるときは元に戻す(次の章で詳しく扱う)
return () => {
document.title = "React 入門";
};
}, [count]); // count が変わったときだけ実行される
return (
<div className="flex items-center gap-3">
<span className="font-mono text-lg font-semibold tabular-nums">
{count}
</span>
<Button size="sm" onClick={() => setCount((c) => c + 1)}>
押す(タブ名を見て)
</Button>
</div>
);
}いつ実行されるか
useEffect に渡した処理は、画面が表示されたあとに実行されます。 コンポーネントの関数を実行している最中ではありません。
useEffect(() => {
// 画面が出たあとに実行される
}, [count]);第 2 引数の配列は依存配列と呼ばれます。 ここに書いた値が前回と変わっていたときだけ実行されます。
useEffect(fn, [count]); // count が変わったときだけ
useEffect(fn, []); // 最初の 1 回だけ
useEffect(fn); // 毎回(ほとんど使わない)title-sync.tsx
"use client";
import { useTrackDemoRender } from "@/components/lesson/demo-card";
import { Button } from "@/components/ui/button";
import { useEffect, useState } from "react";
export function TitleSync() {
// このカードが描き直された回数を数えるための 1 行(教材の仕掛け)
useTrackDemoRender();
const [count, setCount] = useState(0);
useEffect(() => {
// ブラウザのタブ名は React の管理外。ここで直接書き換える
document.title = `${count} 回押されました`;
// このページを離れるときは元に戻す(次の章で詳しく扱う)
return () => {
document.title = "React 入門";
};
}, [count]); // count が変わったときだけ実行される
return (
<div className="flex items-center gap-3">
<span className="font-mono text-lg font-semibold tabular-nums">
{count}
</span>
<Button size="sm" onClick={() => setCount((c) => c + 1)}>
押す(タブ名を見て)
</Button>
</div>
);
}「〜したとき」ではなく「〜の状態に合わせる」
useEffect を誤用しないためのコツは、言葉の使い方にあります。
// ✕ こう考えると誤用しやすい
「ボタンが押されたときに実行したい」
「count が変わったときに実行したい」
// ○ こう考える
「タブ名を、いまの count に合わせておきたい」前者はタイミングの話です。 そしてタイミングの話なら、たいていイベントハンドラに書けます。 ボタンが押されたときの処理は onClick の中です。
後者は状態を合わせる話です。 「どういうきっかけであれ、count がこの値なら、外側もこうなっていてほしい」。 この形になっているときだけ、useEffect が正しい選択になります。
title-sync.tsx
"use client";
import { useTrackDemoRender } from "@/components/lesson/demo-card";
import { Button } from "@/components/ui/button";
import { useEffect, useState } from "react";
export function TitleSync() {
// このカードが描き直された回数を数えるための 1 行(教材の仕掛け)
useTrackDemoRender();
const [count, setCount] = useState(0);
useEffect(() => {
// ブラウザのタブ名は React の管理外。ここで直接書き換える
document.title = `${count} 回押されました`;
// このページを離れるときは元に戻す(次の章で詳しく扱う)
return () => {
document.title = "React 入門";
};
}, [count]); // count が変わったときだけ実行される
return (
<div className="flex items-center gap-3">
<span className="font-mono text-lg font-semibold tabular-nums">
{count}
</span>
<Button size="sm" onClick={() => setCount((c) => c + 1)}>
押す(タブ名を見て)
</Button>
</div>
);
}理解できたか確かめる
確認クイズ
useEffect は何のための道具?
確認クイズ
依存配列に書く値を書き忘れるとどうなる?
title-sync.tsx
"use client";
import { useTrackDemoRender } from "@/components/lesson/demo-card";
import { Button } from "@/components/ui/button";
import { useEffect, useState } from "react";
export function TitleSync() {
// このカードが描き直された回数を数えるための 1 行(教材の仕掛け)
useTrackDemoRender();
const [count, setCount] = useState(0);
useEffect(() => {
// ブラウザのタブ名は React の管理外。ここで直接書き換える
document.title = `${count} 回押されました`;
// このページを離れるときは元に戻す(次の章で詳しく扱う)
return () => {
document.title = "React 入門";
};
}, [count]); // count が変わったときだけ実行される
return (
<div className="flex items-center gap-3">
<span className="font-mono text-lg font-semibold tabular-nums">
{count}
</span>
<Button size="sm" onClick={() => setCount((c) => c + 1)}>
押す(タブ名を見て)
</Button>
</div>
);
}この章のまとめ
useEffectはReact の外側と状態を合わせるための道具- 実行されるのは、画面が表示されたあと
- 依存配列に書いた値が変わったときだけ実行される。 使っている値は全部書く
- 「〜したとき」ではなく「〜に合わせておきたい」と言えるかで判断する
title-sync.tsx
"use client";
import { useTrackDemoRender } from "@/components/lesson/demo-card";
import { Button } from "@/components/ui/button";
import { useEffect, useState } from "react";
export function TitleSync() {
// このカードが描き直された回数を数えるための 1 行(教材の仕掛け)
useTrackDemoRender();
const [count, setCount] = useState(0);
useEffect(() => {
// ブラウザのタブ名は React の管理外。ここで直接書き換える
document.title = `${count} 回押されました`;
// このページを離れるときは元に戻す(次の章で詳しく扱う)
return () => {
document.title = "React 入門";
};
}, [count]); // count が変わったときだけ実行される
return (
<div className="flex items-center gap-3">
<span className="font-mono text-lg font-semibold tabular-nums">
{count}
</span>
<Button size="sm" onClick={() => setCount((c) => c + 1)}>
押す(タブ名を見て)
</Button>
</div>
);
}