繁体   English   中英

每秒更新 state in useEffect 10 秒

[英]Updating state in useEffect after every second for 10 seconds

通过不进入实际用法,我创建了一个简单的示例来解释我想要做什么。

我有一个 state object {num:0}我想在每秒 10 秒后更新 num,据此,我创建了一个运行良好的 class 组件。

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>
    </>
  );
};

谁能帮我解决我在这里做错的事情?

您的所有超时都会运行,但是因为您在第一次渲染时设置了所有超时,所以您围绕初始state.num值创建了一个闭包,因此当每个超时触发时,它将新的 state 值设置为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>

当您创建一个钩子时,它会在创建钩子时捕获 state(即它创建一个闭包)。 创建计时器时state.num的值为 0,因此每个超时都会将 state 设置为0 + 1

对于您的特定问题,最简单的解决方法是使用setState的其他版本,它允许您传递一个回调,该回调根据之前的值修改 state:

React.useEffect(() => {
  for (let i = 0; i < 10; i++) {
    setTimeout(() => setState(prev => ({ num: prev.num + 1 })), i * 1000);
  }
}, []);

这样,您就不会在创建钩子时捕获 state 的值。

正如其中一条评论所指出的 - 如果您的组件卸载,您还应该从挂钩返回清理 function 以停止计时器 - 但这不在您问题的 scope 范围内。

要解决此问题,您必须使用“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;

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM