useEffect 中的状态不更新
State within useEffect not updating
我目前正在用 ReactJS 构建一个计时器进行练习。目前我有两个组件,一个 Timer
组件,它显示时间并在安装时设置间隔。我还有一个 App
组件,它跟踪主要状态,例如计时器是否为 paused
的状态以及 timer
.
的当前值
我的目标是当我单击 pause
按钮时,计时器停止递增。目前我试图通过使用来实现这一点:
if(!paused)
tick(time => time+1);
在我看来,当 paused
为 false
时,应该只增加 time
状态。但是,当我通过单击按钮更新 paused
状态时,setTimeout 中的 paused
状态不会改变。我猜 setTimeout 正在对 paused
状态形成一个闭包,所以当状态改变时它不会更新。我尝试将 paused
添加为 useEffect
的依赖项,但这会导致在 paused
更改时排队多次超时。
const {useState, useEffect} = React;
const Timer = ({time, paused, tick}) => {
useEffect(() => {
const timer = setInterval(() => {
if(!paused) // `paused` doesn't change?
tick(time => time+1);
}, 1000);
}, []);
return <p>{time}s</p>
}
const App = () => {
const [time, setTime] = useState(0);
const [paused, setPaused] = useState(false);
const togglePaused = () => setPaused(paused => !paused);
return (
<div>
<Timer paused={paused} time={time} tick={setTime} />
<button onClick={togglePaused}>{paused ? 'Play' : 'Pause'}</button>
</div>
);
}
ReactDOM.render(<App />, document.body);
<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>
那么,我的问题是:
- 为什么我当前的代码不起作用(为什么
paused
没有在 useEffect
内更新)?
- 有什么方法可以使上述代码正常工作,以便我可以“暂停”我在
useEffect()
内设置的时间间隔?
停止定时器return函数从useEffect()
.
清除间隔
React performs the cleanup when the component unmounts. However, as we learned earlier, effects run for every render and not just once. This is why React also cleans up effects from the previous render before running the effects next time.
您还应该在 dependencies 数组中传递 paused
以停止 useEffect
在每次重新渲染时创建新的间隔。如果你添加 [paused]
它只会在 paused
变化时创建新的间隔。
const {useState, useEffect} = React;
const Timer = ({time, paused, tick}) => {
useEffect(() => {
const timer = setInterval(() => {
if(!paused) // `paused` doesn't change?
tick(time => time+1);
}, 1000);
return () => clearInterval(timer);
}, [paused]);
return <p>{time}s</p>
}
const App = () => {
const [time, setTime] = useState(0);
const [paused, setPaused] = useState(false);
const togglePaused = () => setPaused(paused => !paused);
return (
<div>
<Timer paused={paused} time={time} tick={setTime} />
<button onClick={togglePaused}>{paused ? 'Play' : 'Pause'}</button>
</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>
setInterval 第一次捕获 paused
值,您需要删除间隔,每次更改暂停时重新创建它。
您可以查看这篇文章了解更多信息:https://overreacted.io/making-setinterval-declarative-with-react-hooks
您可以使用 Dan 的 useInterval
钩子。我无法比他更好地解释它 why 你应该使用它。
钩子看起来像这样。
function useInterval(callback, delay) {
const savedCallback = React.useRef();
// Remember the latest callback.
React.useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
React.useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
然后像这样在您的 Timer
组件中使用它。
const Timer = ({ time, paused, tick }) => {
useInterval(() => {
if (!paused) tick(time => time + 1);
}, 1000);
return <p>{time}s</p>;
};
我认为不同之处在于 useInterval
钩子知道它的依赖关系(暂停),而 setInterval
不是。
我目前正在用 ReactJS 构建一个计时器进行练习。目前我有两个组件,一个 Timer
组件,它显示时间并在安装时设置间隔。我还有一个 App
组件,它跟踪主要状态,例如计时器是否为 paused
的状态以及 timer
.
我的目标是当我单击 pause
按钮时,计时器停止递增。目前我试图通过使用来实现这一点:
if(!paused)
tick(time => time+1);
在我看来,当 paused
为 false
时,应该只增加 time
状态。但是,当我通过单击按钮更新 paused
状态时,setTimeout 中的 paused
状态不会改变。我猜 setTimeout 正在对 paused
状态形成一个闭包,所以当状态改变时它不会更新。我尝试将 paused
添加为 useEffect
的依赖项,但这会导致在 paused
更改时排队多次超时。
const {useState, useEffect} = React;
const Timer = ({time, paused, tick}) => {
useEffect(() => {
const timer = setInterval(() => {
if(!paused) // `paused` doesn't change?
tick(time => time+1);
}, 1000);
}, []);
return <p>{time}s</p>
}
const App = () => {
const [time, setTime] = useState(0);
const [paused, setPaused] = useState(false);
const togglePaused = () => setPaused(paused => !paused);
return (
<div>
<Timer paused={paused} time={time} tick={setTime} />
<button onClick={togglePaused}>{paused ? 'Play' : 'Pause'}</button>
</div>
);
}
ReactDOM.render(<App />, document.body);
<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>
那么,我的问题是:
- 为什么我当前的代码不起作用(为什么
paused
没有在useEffect
内更新)? - 有什么方法可以使上述代码正常工作,以便我可以“暂停”我在
useEffect()
内设置的时间间隔?
停止定时器return函数从useEffect()
.
React performs the cleanup when the component unmounts. However, as we learned earlier, effects run for every render and not just once. This is why React also cleans up effects from the previous render before running the effects next time.
您还应该在 dependencies 数组中传递 paused
以停止 useEffect
在每次重新渲染时创建新的间隔。如果你添加 [paused]
它只会在 paused
变化时创建新的间隔。
const {useState, useEffect} = React;
const Timer = ({time, paused, tick}) => {
useEffect(() => {
const timer = setInterval(() => {
if(!paused) // `paused` doesn't change?
tick(time => time+1);
}, 1000);
return () => clearInterval(timer);
}, [paused]);
return <p>{time}s</p>
}
const App = () => {
const [time, setTime] = useState(0);
const [paused, setPaused] = useState(false);
const togglePaused = () => setPaused(paused => !paused);
return (
<div>
<Timer paused={paused} time={time} tick={setTime} />
<button onClick={togglePaused}>{paused ? 'Play' : 'Pause'}</button>
</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>
setInterval 第一次捕获 paused
值,您需要删除间隔,每次更改暂停时重新创建它。
您可以查看这篇文章了解更多信息:https://overreacted.io/making-setinterval-declarative-with-react-hooks
您可以使用 Dan 的 useInterval
钩子。我无法比他更好地解释它 why 你应该使用它。
钩子看起来像这样。
function useInterval(callback, delay) {
const savedCallback = React.useRef();
// Remember the latest callback.
React.useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
React.useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
然后像这样在您的 Timer
组件中使用它。
const Timer = ({ time, paused, tick }) => {
useInterval(() => {
if (!paused) tick(time => time + 1);
}, 1000);
return <p>{time}s</p>;
};
我认为不同之处在于 useInterval
钩子知道它的依赖关系(暂停),而 setInterval
不是。