通过不深入实际使用,我创建了一个简单的例子来解释我想要做什么。
我有一个状态对象 {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>
</>
);
};
任何人都可以帮我解决我在这里做错的事情吗?
答案 0 :(得分:1)
你所有的超时do都会运行,但是因为你在第一次渲染时设置了它们,你已经在初始 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()
调用以避免创建闭包,但您放弃了通过设置 new 所允许的细粒度控制每次渲染超时。
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>
答案 1 :(得分:0)
当你创建一个钩子时,它会捕获钩子被创建时的状态(即它创建一个闭包)。创建计时器时 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);
}
}, []);
这样,您就不会在创建钩子时捕获状态的值。
正如其中一条评论所指出的 - 如果您的组件卸载,您还应该从钩子返回一个清理函数以停止计时器 - 但这超出了您的问题范围。
答案 2 :(得分:-1)
要解决这个问题,您必须使用“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;