web-dev-qa-db-ja.com

setInterval内でReact状態フックを使用すると状態が更新されない

私は新しい React Hooks を試しており、毎秒増加するはずのカウンターを備えたClockコンポーネントを持っています。ただし、値が1を超えて増加することはありません。

function Clock() {
  const [time, setTime] = React.useState(0);
  React.useEffect(() => {
    const timer = window.setInterval(() => {
      setTime(time + 1);
    }, 1000);
    return () => {
      window.clearInterval(timer);
    };
  }, []);

  return (
    <div>Seconds: {time}</div>
  );
}

ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>

<div id="app"></div>
44
Yangshun Tay

理由は、setIntervalのクロージャーに渡されたコールバックは最初のレンダリングでtime変数にのみアクセスし、後続のレンダリングではuseEffect()の新しいtime値にアクセスできないためです。 2回目は呼び出されません。

timeは、setIntervalコールバック内で常に0の値を持ちます。

よく知っているsetStateと同様に、状態フックには2つの形式があります。1つは更新された状態を取り、もう1つは現在の状態が渡されるコールバックフォームです。 setStateコールバック内の値を使用して、インクリメントする前に最新の状態値を保持します。

ボーナス:代替アプローチ

Dan Abramovは、フックでsetIntervalを使用する ブログ投稿 のトピックについて詳しく説明し、この問題を回避する別の方法を提供します。それを読むことを強くお勧めします!

function Clock() {
  const [time, setTime] = React.useState(0);
  React.useEffect(() => {
    const timer = window.setInterval(() => {
      setTime(prevTime => prevTime + 1); // <-- Change this line!
    }, 1000);
    return () => {
      window.clearInterval(timer);
    };
  }, []);

  return (
    <div>Seconds: {time}</div>
  );
}

ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>

<div id="app"></div>
66
Yangshun Tay

useEffect関数は、空の入力リストが提供されている場合、コンポーネントのマウント時に1回だけ評価されます。

setIntervalに代わる方法は、状態が更新されるたびにsetTimeoutで新しい間隔を設定することです:

  const [time, setTime] = React.useState(0);
  React.useEffect(() => {
    const timer = setTimeout(() => {
      setTime(time + 1);
    }, 1000);
    return () => {
      clearTimeout(timer);
    };
  }, [time]);

setTimeoutのパフォーマンスへの影響はわずかであり、通常は無視できます。新しく設定されたタイムアウトが望ましくない影響を引き起こすポイントに対してコンポーネントが時間に敏感でない限り、setIntervalsetTimeoutの両方のアプローチが受け入れられます。

8
Estus Flask

別の解決策は、useReducerを使用することです。常に現在の状態が渡されるためです。

function Clock() {
  const [time, dispatch] = React.useReducer((state = 0, action) => {
    if (action.type === 'add') return state + 1
    return state
  });
  React.useEffect(() => {
    const timer = window.setInterval(() => {
      dispatch({ type: 'add' });
    }, 1000);
    return () => {
      window.clearInterval(timer);
    };
  }, []);

  return (
    <div>Seconds: {time}</div>
  );
}

ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>

<div id="app"></div>
2
Bear-Foot

時間が変更されたらReact再レンダリングを指示します。 オプトアウト

function Clock() {
  const [time, setTime] = React.useState(0);
  React.useEffect(() => {
    const timer = window.setInterval(() => {
      setTime(time + 1);
    }, 1000);
    return () => {
      window.clearInterval(timer);
    };
  }, [time]);

  return (
    <div>Seconds: {time}</div>
  );
}

ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>

<div id="app"></div>
0
sumail

変数を取得し、それを更新するだけでなくいくつかのことをする必要があるため、この解決策は私にはうまくいきません。

フックの更新された値を約束して取得する回避策を取得します

例えば:

async function getCurrentHookValue(setHookFunction) {
  return new Promise((resolve) => {
    setHookFunction(prev => {
      resolve(prev)
      return prev;
    })
  })
}

これにより、このようにsetInterval関数内の値を取得できます

let dateFrom = await getCurrentHackValue(setSelectedDateFrom);
0