Closed6

useEffectについて

MAAAAAAAAAAAMAAAAAAAAAAA

以下をもとに理解を進めます

// 1. インポート部分
import type { FC } from 'react';
import { useEffect, useState } from 'react';
import { RepeatClockIcon as ResetIcon } from '@chakra-ui/icons';
import { Box, Button, Stat, StatLabel, StatNumber } from '@chakra-ui/react';

// 2. プロップの型定義とデフォルト値
type Props = { maxCount?: number };
const MAX_COUNT = 60;

// 3. コンポーネントの定義
const Timer: FC<Props> = ({ maxCount = MAX_COUNT }) => {
  // 4. Stateの定義
  const [timeLeft, setTimeLeft] = useState(maxCount);

  // 5. ヘルパー関数の定義
  const tick = (): void => setTimeLeft((t) => t - 1);
  const reset = (): void => setTimeLeft(maxCount);

  // 6. useEffectフック
  useEffect(() => {
    const timerId = setInterval(tick, 1000);
    return () => clearInterval(timerId);
  }, []);

  useEffect(() => {
    if (timeLeft === 0) setTimeLeft(maxCount);
  });

  // 7. レンダリング
  return (
    <Box p={5} w="sm" borderWidth="1px" borderRadius="lg" boxShadow="base">
      <Stat mb={2}>
        <StatLabel fontSize={18}>Count</StatLabel>
        <StatNumber fontSize={42}>{timeLeft}</StatNumber>
      </Stat>
      <Button w="xs" colorScheme="red" variant="solid" leftIcon={<ResetIcon />} onClick={reset}>
        Reset
      </Button>
    </Box>
  );
};

export default Timer;

このコンポーネントは、指定された最大時間からカウントダウンを開始し、時間が0になったらリセットボタンを押すことで時間を再設定するか、自動的に再設定される機能を持つタイマーコンポーネントです。

MAAAAAAAAAAAMAAAAAAAAAAA

以下、ESLintでの警告が出る。

useEffect(() => {
  if (timeLeft === 0) setTimeLeft(maxCount);
});

警告の内容として、setTimeLeftを使うuseEffectの中に依存配列が無いため、無限の再レンダリングが発生する可能性があるというものでした。

この警告は、timeLeftmaxCountの値に基づいて何らかの副作用を起こしているのに、それらの変数を依存配列に追加していないために発生しています。

ステップ1: 依存配列を追加する

まず、警告に従い、timeLeftmaxCountを依存配列に追加します。

useEffect(() => {
  if (timeLeft === 0) setTimeLeft(maxCount);
}, [timeLeft, maxCount]);

ステップ2: 無限ループの理解

ここで疑問が生まれるかもしれません:「なぜこれが無限の再レンダリングにつながる可能性があるのか?」

setTimeLeft(maxCount)が実行されると、timeLeftの値がmaxCountにリセットされます。これにより、コンポーネントは再レンダリングされます。依存配列が設定されていないと、このuseEffectは再レンダリングの度に毎回実行されるため、timeLeftが0でなくてもsetTimeLeft(maxCount)が実行される可能性があります。これは、期待しない挙動や無限の更新を引き起こす可能性があります。

ステップ3: 依存配列の重要性

依存配列に変数を追加することで、その変数の値が変更されたときにのみuseEffect内の副作用が実行されるようになります。この例では、timeLeftが0になったとき、またはmaxCountが変更されたときにのみ副作用が実行されます。

このように、依存配列を正確に設定することで、期待する動作を安全に実現することができ、不要な副作用の実行や無限の再レンダリングのリスクを回避することができます。

MAAAAAAAAAAAMAAAAAAAAAAA

useEffect と再レンダリング

React では、state または props の変更があると、コンポーネントは再レンダリングされます。useEffectは、コンポーネントがレンダリングされるたびにデフォルトで実行されます。ただし、useEffectの第2引数(依存配列)を使用することで、特定の変数の変更時のみ副作用を実行するように制限することができます。

依存配列がない場合

もしuseEffectが以下のように書かれていた場合:

useEffect(() => {
  if (timeLeft === 0) setTimeLeft(maxCount);
});

このuseEffectは、コンポーネントが再レンダリングされるたびに実行されます。そして、timeLeftが0のとき、setTimeLeft(maxCount)が実行され、timeLeftの値がmaxCountに更新されます。この更新により、再度コンポーネントが再レンダリングされます。しかし、この新しいレンダリングにおいても、上記のuseEffectは再び実行されます。

無限ループのリスク

上記のuseEffectの設定では、timeLeftが0でなくても、再レンダリングが発生するたびにuseEffectが実行されるため、意図しない更新や無限ループが発生するリスクがあります。

例えば、timeLeftが0でないにもかかわらず、他の理由でコンポーネントが再レンダリングされた場合、上記のuseEffectは毎回実行され、結果としてsetTimeLeft(maxCount)が実行される可能性があります。

依存配列の利点

これを避けるためには、依存配列を使用して、timeLeftまたはmaxCountが変更されたときのみuseEffectが実行されるようにする必要があります:

useEffect(() => {
  if (timeLeft === 0) setTimeLeft(maxCount);
}, [timeLeft, maxCount]);

このように設定することで、timeLeftまたはmaxCountの値が変わったときのみ副作用が実行され、不要な更新や無限ループのリスクを防ぐことができます。

MAAAAAAAAAAAMAAAAAAAAAAA

「コンポーネントが再レンダリングされる」というのは、コンポーネントのstatepropsが変更されたとき、または親コンポーネントが再レンダリングされたときなど、さまざまな要因でコンポーネントのレンダリングが再度行われることを指します。

useEffectはデフォルトでコンポーネントの再レンダリングが行われるたびに実行されます。しかし、useEffectの第2引数(依存配列)を指定することで、特定の変数の変更時だけ副作用が実行されるように制限することができます。

したがって、例として挙げたuseEffect

useEffect(() => {
  if (timeLeft === 0) setTimeLeft(maxCount);
});

このようになっている場合、このuseEffectはコンポーネントが再レンダリングされるたび(statepropsの変更など、どんな要因であれ)に実行されます。これが無限ループのリスクを生む要因となります。

逆に、依存配列を持つuseEffect

useEffect(() => {
  if (timeLeft === 0) setTimeLeft(maxCount);
}, [timeLeft, maxCount]);

は、timeLeftまたはmaxCountの値が変わった場合のみ実行されます。それ以外の再レンダリングの要因で実行されることはありません。

このスクラップは2023/10/30にクローズされました