代码在这里:https://codesandbox.io/s/nw4jym4n0
export default ({ name }: Props) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCounter(counter + 1);
}, 1000);
return () => {
clearInterval(interval);
};
});
return <h1>{counter}</h1>;
};
问题是每个setCounter
触发器都会重新渲染,因此间隔会被重置并重新创建。由于状态(计数器)一直在增加,所以这看起来不错,但是当与其他钩子结合使用时,它可能会冻结。
正确的方法是什么?在类组件中,使用持有间隔的实例变量很简单。
答案 0 :(得分:10)
您想为useEffect
提供一个空数组作为第二个参数,以便该函数在初始渲染后仅运行一次。
由于闭包是如何工作的,这将使counter
变量始终引用初始值。您可以使用setCounter
的函数版本来始终获取正确的值。
示例
const { useState, useEffect } = React;
function App() {
const [counter, setCounter] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCounter(counter => counter + 1);
}, 1000);
return () => {
clearInterval(interval);
};
}, []);
return <h1>{counter}</h1>;
};
ReactDOM.render(
<App />,
document.getElementById('root')
);
<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="root"></div>
答案 1 :(得分:1)
正确的方法是只运行一次效果。由于只需要运行一次效果,因为在安装过程中,您可以传入一个空数组作为第二个参数来实现。
但是,您将需要更改setCounter
以使用counter
的先前值。原因是因为传递到setInterval
的闭包中的回调仅访问第一个渲染中的counter
变量,而无法访问后续渲染中的新counter
值,因为useEffect()
不会第二次被调用;在counter
回调中,setInterval
的值始终为0。
就像您熟悉的setState
一样,状态挂钩有两种形式:一种是采用更新状态的形式,另一种是将当前状态传入的回调形式。您应该使用第二种形式并阅读setState
回调中的最新状态值,以确保在递增之前具有最新状态值。
function Counter() {
const [counter, setCounter] = React.useState(0);
React.useEffect(() => {
const timer = setInterval(() => {
setCounter(prevCount => prevCount + 1); // <-- Change this line!
}, 1000);
return () => {
clearInterval(timer);
};
}, []); // Pass in empty array to run effect only once!
return (
<div>Count: {counter}</div>
);
}
ReactDOM.render(<Counter />, 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>
答案 2 :(得分:0)
正如另一个答案已经显示的那样,可以使useEffect
回调仅运行一次,并且与componentDidMount
类似地工作。在这种情况下,由于功能范围带来的限制,必须使用状态更新程序功能,否则更新后的counter
在setInterval
回调中将不可用。
另一种方法是在每次计数器更新上运行useEffect
回调。在这种情况下,应将setInterval
替换为setTimeout
,并将更新限制为counter
更新:
export default ({ name }: Props) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
const timeout = setTimeout(() => {
setCounter(counter + 1);
}, 1000);
return () => {
clearTimeout(timeout);
};
}, [counter]);
return <h1>{counter}</h1>;
};