🎃

【React】再レンダリングとuseEffect

に公開3
  • Stateの値や、Propsの値、親のコンポーネントが更新された場合に再レンダリングされる

App.jsx

import ( useState ) from 'react';
import ( Message ) from './components/Message';

export const App = () => {
    const [num, setNum] = useState(0);
    const [isMultiple, setIsMultiple] = useState(false); // 状態を変えるのでuseStateを使う
    const onClickCount = () => {
        setNum((prev) => prev + 1);
    };
    const onClickToggle = () => {
        setIsMultiple(!isMultiple); // セット関数を読んで実行。反対のフラグにする
    };

    useEffect(() => {
        if (num > 0) {
            if (num % 5 === 0) {
                isMultiple || setIsMultiple(true);
            } else {
                isMultiple && setIsMultiple(false);
            }
        }
    }, [num]); // useEffectの第2引数の配列に変更があった時のみ、再レンダリングされる。空の配列の場合、最初のマウント時だけ実行される。処理の関心を切り替えられる。

    return (
        <>
            <Message color='orange'>How's it going?</ Message>
            <Message color='green'>Pretty good!</ Message>
            <button onClick={onClickCount}>カウント</button>
            <p>{num}</p>
            <button onClick={onClickToggle}>on/off</button>
            {isMultiple && <p>5の倍数!!</p>} // 特定の条件で要素を表示する論理演算子
        </>
    );
};

Discussion

Honey32Honey32

説明していると筆が乗ってしまったので長文で失礼します💦

React における「レンダー」という用語が指すものは「useEffect の実行」ではありません。

「レンダー」という言葉が指しているのは、App 関数を、関数として実行する(App() のように)ことであって、

「レンダー」とは、React がコンポーネントを呼び出すことです。

https://ja.react.dev/learn/render-and-commit#step-2-react-renders-your-components

useEffect 内のコードの実行は、レンダーと呼ばれていません。

コンポーネントがレンダーされるたびに、React は画面を更新し、その後で useEffect 内のコードを実行します。

https://ja.react.dev/learn/synchronizing-with-effects#step-1-declare-an-effect

Honey32Honey32

加えて、

// useEffectの第2引数の配列に変更があった時のみ、再レンダリングされる。空の配列の場合、最初のマウント時だけ実行される。処理の関心を切り替えられる。

とありますが、これも誤りです。依存配列は、対象の処理の実行タイミングを恣意的にコントロールするために使うものではありません。

多くのユーザはこのコードを「マウント時に roomId に接続し、roomId が変更されるたびに古いルームから切断して接続を再確立する」のように読んでしまいます。しかしこれでは、コンポーネントのライフサイクルの視点から考えてしまっています。

https://ja.react.dev/blog/2025/04/23/react-labs-view-transitions-activity-and-more#effects-without-dependencies

代わりに、エフェクトの視点から考える方がベターです。エフェクトはコンポーネントのライフサイクルについて知りません。同期を開始する方法と停止する方法が記述されているだけです。ユーザがこのようにエフェクトを考えることでエフェクトは書きやすくなり、必要次第で何度も開始・停止されることに対して、より頑強になります。

https://ja.react.dev/blog/2025/04/23/react-labs-view-transitions-activity-and-more#effects-without-dependencies

手前味噌になりますが、僕の記事も見てもらえば、どう考えるべきかわかりやすいと思います。

https://qiita.com/honey32/items/58e56e407d4d87e294a4

https://zenn.dev/yumemi_inc/articles/react-effect-simply-explained

Honey32Honey32

参考までに、コード例については、おそらく以下のように修正できると思います。

このほうが「いつ、どのようにステートが更新されるか」を正確に記述できているのでベターです。

コード例は以下のように書くほうがベターです。

-   const [num, setNum] = useState(0);
+   const [{ num, isMultiple }, setState] = useState({ num: 0, isMultiple: false });
-   const [isMultiple, setIsMultiple] = useState(false); // 状態を変えるのでuseStateを使う
    const onClickCount = () => {
-       setNum((prev) => prev + 1);
+       // num を更新し、更新語の値が 5 の倍数であるかどうかの真偽値で isMultiple も更新する  
+       setState((prev) => {
+         const num = prev.num + 1;
+         return { num, isMultiple: num % 5 === 0 };
+       });
    };
    const onClickToggle = () => {
-       setIsMultiple(!isMultiple); // セット関数を読んで実行。反対のフラグにする
+       // isMultiple のフラグのみ、反転させる
+       setState(({ num, isMultiple }) => ({ num, isMultiple: !isMultiple }));
    };

-   useEffect(() => {
-      // 中略
-    }, [num]); // useEffectの第2引数の配列に変更があった時のみ、再レンダリングされる。空の配列の場合、最初のマウント時だけ実行される。処理の関心を切り替えられる。

イベントハンドラとエフェクトのどちらにロジックを入れるべきか選択する際には、ユーザの観点からそれがどのようなロジックなのかを自問自答するようにしましょう。そのロジックが特定のユーザ操作によって引き起こされる場合は、イベントハンドラに保持します。

https://ja.react.dev/learn/you-might-not-need-an-effect#sending-a-post-request

このような場合、レンダー中に計算できるものはそこで行い、イベントハンドラで state の調整を終わらせる方が良いでしょう。

この方がはるかに効率的です。また、ゲームの履歴を表示する方法を実装する場合でも、各 state 変数を過去の手順の時点の値に設定できるようになり、エフェクトが連鎖して他のすべての state が勝手に書き換わるようなことを避けられます。

https://ja.react.dev/learn/you-might-not-need-an-effect#chains-of-computations