clearInterval 不会在 React.useEffect 钩子中被调用

clearInterval does not get called inside React.useEffect hook

我的 React 游戏有一个 <Clock/> 组件来跟踪时间。

游戏暂停时计时器应该停止。

我正在使用 Redux 来管理 play/pause 状态,以及经过的时间。

const initialState = { inProgress: false, timeElapsed: 0 }

inProgress 状态由另一个组件上的按钮处理,该按钮调度更新商店的操作(仅针对 inProgress 值)。

<Clock/> 组件在其 useEffect 挂钩中递增 timeElapsed setInterval。还是不清楚。

import React from 'react';
import { connect } from 'react-redux';

const Clock = ({ dispatch, inProgress, ticksElapsed }) => {

    React.useEffect(() => {

        const progressTimer = setInterval(function(){
            inProgress ? dispatch({ type: "CLOCK_RUN" }) : clearInterval(progressTimer);
        }, 1000)

    }, [inProgress]);

    return (
        <></>
    )
};

let mapStateToProps = ( state ) => {
    let { inProgress, ticksElapsed } = state.gameState;
    return { inProgress, ticksElapsed };
}

export default connect(
    mapStateToProps,
    null,
)(Clock);

setInterval 中,当 inProgressfalse 时,我希望 clearInterval(progressTimer) 停止计时。

此外,还有另一个问题,即在 useEffect 挂钩中遗漏 [inProgress] 会导致计时器以荒谬的速度增加,从而导致应用程序崩溃。

谢谢。

对于传递给 setInterval 的函数,inProgress 是一个 stale closure

在cleanup函数中清除interval即可解决:

const Clock = ({ dispatch, inProgress, ticksElapsed }) => {
  React.useEffect(() => {
    const progressTimer = setInterval(function () {
      inProgress && dispatch({ type: 'CLOCK_RUN' });
    }, 500);
    return () =>
      //inprogress is stale so when it WAS true
      //  it must now be false for the cleanup to
      //  be called
      inProgress && clearInterval(progressTimer);
  }, [dispatch, inProgress]);

  return <h1>{ticksElapsed}</h1>;
};

const App = () => {
  const [inProgress, setInProgress] = React.useState(false);
  const [ticksElapsed, setTicksElapsed] = React.useState(0);
  const dispatch = React.useCallback(
    () => setTicksElapsed((t) => t + 1),
    []
  );
  return (
    <div>
      <button onClick={() => setInProgress((p) => !p)}>
        {inProgress ? 'stop' : 'start'}
      </button>
      <Clock
        inProgress={inProgress}
        dispatch={dispatch}
        ticksElapsed={ticksElapsed}
      />
    </div>
  );
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>