通过不进入实际用法,我创建了一个简单的示例来解释我想做什么。
我有一个状态对象{num:0}
,我想在10秒后每秒钟更新一个num,根据这个结果,我创建了一个非常好的类组件。
class App extends React.Component {
constructor() {
super();
this.state = {
num: 0
};
}
componentDidMount = () => {
for (let i = 0; i < 10; i++) {
setTimeout(() => this.setState({ num: this.state.num + 1 }), i * 1000);
}
};
render() {
return (
<>
<p>hello</p>
<p>{this.state.num}</p>
</>
);
}
}
现在,我想在功能组件中复制相同的功能,但我无法复制。我尝试了如下所示:
const App = () => {
const [state, setState] = React.useState({ num: 0 });
React.useEffect(() => {
for (let i = 0; i < 10; i++) {
setTimeout(() => setState({ num: state.num + 1 }), i * 1000);
}
}, []);
return (
<>
<p>hello</p>
<p>{state.num}</p>
</>
);
};
有人能帮我解决我在这里做的错事吗?
发布于 2021-06-13 14:00:38
所有超时都会运行,但由于在第一次呈现时设置了所有超时,所以在初始state.num
值周围创建了一个闭包,因此,当每一次触发时,它都将新的状态值设置为0 + 1
,而不会发生任何更改。
注释中提到的重复内容涵盖了详细信息,但是这里有一个使用ref
作为计数器的快速工作片段,在10次迭代后停止,并清除useEffect返回时的计时器。
const App = () => {
const [state, setState] = React.useState({ num: 0 });
const counter = React.useRef(0);
React.useEffect(() => {
if (counter.current < 10) {
counter.current += 1;
const timer = setTimeout(() => setState({ num: state.num + 1 }), 1000);
return () => clearTimeout(timer);
}
}, [state]);
return (
<div>
<p>hello</p>
<p>{state.num}</p>
</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>
要使代码按原样工作,可以一次设置所有这些代码,以便将回调传递给setState()
调用,以避免创建闭包,但在每次呈现时设置新的超时将放弃允许的粒度控制。
const App = () => {
const [state, setState] = React.useState({ num: 0 });
React.useEffect(() => {
for (let i = 0; i < 10; i++) {
setTimeout(() => setState(prevState => ({ ...prevState, num: prevState.num + 1 })), i * 1000);
}
}, []);
return (
<div>
<p>hello</p>
<p>{state.num}</p>
</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>
发布于 2021-06-13 14:05:52
创建钩子时,它捕获创建钩子时的状态(即创建闭包)。在创建计时器时,state.num
的值为0,因此每个超时都将状态设置为0 + 1
。
针对特定问题的最简单的修复方法是use the other version of setState
which allows you to pass a callback,它根据以前的值修改状态:
React.useEffect(() => {
for (let i = 0; i < 10; i++) {
setTimeout(() => setState(prev => ({ num: prev.num + 1 })), i * 1000);
}
}, []);
这样,您就不会在创建钩子时捕获状态的值。
正如其中一个注释所指出的,您还应该从钩子中返回一个清理函数,以便在组件卸载时停止计时器--但这不在您的问题范围之内。
发布于 2021-06-13 14:23:38
要解决这个问题,您必须使用"i“值而不是state.num值。如果使用state.num值,它将始终是0而不是当前值,因为每次在useEffect钩子中重新初始化它。在修正后的代码下面找到,现在它可以工作了。
import React from "react";
const App = () => {
const [state, setState] = React.useState({ num: 0 });
React.useEffect(() => {
for (let i = 0; i < 10; i++) {
setTimeout(() => setState({ num: i + 1 }), i * 1000);
}
}, []);
return (
<>
<p>hello</p>
<p>{state.num}</p>
</>
);
};
export default App;
https://stackoverflow.com/questions/67962821
复制相似问题