我正在尝试新的React Hooks并且有一个带有计数器的Clock组件,该计数器应该每秒增加一次。但是,该值不会超过一个。
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>
原因是因为传递到setInterval
闭包的回调只访问第一个渲染中的time
变量,它在后续渲染中无法访问新的time
值,因为第二次没有调用useEffect()
。
time
在setInterval
回调中的值始终为0。
就像你熟悉的setState
一样,状态钩子有两种形式:一种是它处于更新状态,另一种是传递当前状态的回调形式。你应该使用第二种形式并读取setState
中的最新状态值回调以确保在递增之前具有最新的状态值。
奖金:替代方法
丹·阿布拉莫夫(Dan Abramov)深入探讨了在他的
setInterval
中使用blog post和钩子的主题,并提供了解决这个问题的替代方法。强烈推荐阅读!
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>
当提供空输入列表时,useEffect
函数仅在组件安装时评估一次。
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/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<div id="app"></div>
当时间改变时,告诉React重新渲染.opt out
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>