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/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
理由は、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/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
useEffect
関数は、空の入力リストが提供されている場合、コンポーネントのマウント時に1回だけ評価されます。
setInterval
に代わる方法は、状態が更新されるたびにsetTimeout
で新しい間隔を設定することです:
const [time, setTime] = React.useState(0);
React.useEffect(() => {
const timer = setTimeout(() => {
setTime(time + 1);
}, 1000);
return () => {
clearTimeout(timer);
};
}, [time]);
setTimeout
のパフォーマンスへの影響はわずかであり、通常は無視できます。新しく設定されたタイムアウトが望ましくない影響を引き起こすポイントに対してコンポーネントが時間に敏感でない限り、setInterval
とsetTimeout
の両方のアプローチが受け入れられます。
別の解決策は、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/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
時間が変更されたら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/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
変数を取得し、それを更新するだけでなくいくつかのことをする必要があるため、この解決策は私にはうまくいきません。
フックの更新された値を約束して取得する回避策を取得します
例えば:
async function getCurrentHookValue(setHookFunction) {
return new Promise((resolve) => {
setHookFunction(prev => {
resolve(prev)
return prev;
})
})
}
これにより、このようにsetInterval関数内の値を取得できます
let dateFrom = await getCurrentHackValue(setSelectedDateFrom);