【问题标题】:ReactJs dynamic setState not working in for loopReactJs动态setState在for循环中不起作用
【发布时间】:2020-02-22 22:40:08
【问题描述】:

我在state 中有一个obj,默认情况下都是false

obj: {
  field1: false,
  field2: false,
  field3: false
}

我想在submit之后全部设置为true,都是动态的:

handleSubmit = (e) => {
  e.preventDefault();

  const doValid = {
    field1: true,
    field2: true,
    field3: true,
  }

//this is static, in real code it comes from validator and decide to be false or true

  for (let i = 0; i < Object.keys(doValid).length; i++) {
    this.handleValid(Object.keys(doValid)[i], Object.values(doValid)[i]);
  }
}

这是我将每个state 动态设置为true

handleValid = (type, v) => {

  this.setState({
    ...this.state,
    obj: { ...this.state.obj,
      [type]: [v]
    }
      })

}

如您所见,我为此使用了[type][v],但是问题

  1. 当我点击提交按钮时,状态不会更改为true (我知道更改状态可能是延迟) (如果我正确忽略了这个)
  2. 如果我再次点击,现在我看到最后一项更改为true,但这里有两个问题:

a:为什么最后一项改变了? b:为什么会变成这样?带括号,使它成为array,但它只是一个动态来决定它是真还是假:

field1: false
field2: false
field3: [true]

应该是这样的:

field1: true
field2: true
field3: true

JSFiddle

(请在 jsfiddle 中查看控制台日志)

【问题讨论】:

    标签: javascript reactjs


    【解决方案1】:

    在根据之前的值确定新的状态值时,我们将一个函数传递给 setState 以引用之前的状态值。

    setState() 也接受一个函数。该函数接受组件的先前状态和当前属性,用于计算并返回下一个状态。

      handleValid = (type, v) => {
        this.setState(prevState => ({
          ...prevState,
          obj: { ...prevState.obj, [type]: v },
        }));
      };
    

    为什么要将函数传递给 setState? 问题是,状态更新可能是异步的。

    想想当 setState() 被调用时会发生什么。 React 将首先将您传递给 setState() 的对象合并到当前状态。然后它会开始那个和解的事情。它将创建一个新的 React Element 树(UI 的对象表示),将新树与旧树进行比较,根据您传递给 setState() 的对象找出发生了什么变化,然后最终更新 DOM

    请按照这个关于函数式 setState 以及我们调用 setState() 时发生的美丽解释 link

    【讨论】:

    • 我想使用更简单的代码,但问题是他们删除了 handleValid,我有点困惑如何在这些代码中包含我的验证器。您的代码似乎运行良好,让我检查一下我的项目并选择最佳答案。
    【解决方案2】:

    你需要把[v]改成v。当你使用括号时,它认为它是一个数组,并使其成为一个数组。

    你试图同时改变状态,当你改变它时,你正在复制旧状态,因为它还没有改变。 setState 不会同时发生。

    您需要删除handleValid 函数并使用以下代码更改handleSubmit。

    handleSubmit = (e) => {
                e.preventDefault();
                console.log(this.state.obj);
    
                        const doValid = {
                            field1: true,
                            field2: true,
                            field3: true,
                        }
            //you need to copy the state and change its values. You setState after you are done with it.
    
          let newState = {...this.state};
          for (let i = 0; i < Object.keys(doValid).length; i++) {
              newState.obj[Object.keys(doValid)[i]] = Object.values(doValid)[i];
          }
          //now we made our new state and will update it.
          this.setState(newState, () => {
           //after change successfully happened, this code will fire.
           console.log(this.state.obj);
          })
        }
    

    【讨论】:

    • 糟糕的答案,您制作了状态的浅拷贝,然后改变状态。尝试在控制台中运行以下命令:const person = {address:{street:'org'}}; const copy = {...person}; copy.address.street='changed'; console.log(person.address.street);
    • @HMR 我以为他想在循环数组时更新状态。这就是我写这个的原因,如果只是复制对象,你的代码也有意义。
    • 重点是 newState 是一个浅拷贝,所以只有copy.level1=newValue 不会改变原始状态,而copy.level1.level2 会改变。在 for 循环中进行更新也比将 doValid 传播到状态要复杂得多。我的第一条评论可能有点粗鲁,但我认为这个答案并不是很有用。
    【解决方案3】:

    看起来你写了很多复杂的代码只是为了做:

    this.setState({
      ...this.state,
      obj: { ...this.state.obj, ...doValid },
    });
    

    您似乎无法理解范围,here 是一本很好的书,我建议您阅读整个系列,因为它是一个很棒的系列。

    这是我的代码的一个工作示例:

    class App extends React.Component {
      state = {
        obj: {
          field1: 2,
          field2: 3,
          field3: 4,
        },
      };
      clickHandler = () => {
        const doValid = {
          field1: true,
          field2: true,
          field3: true,
        };
        this.setState({
          ...this.state,
          obj: { ...this.state.obj, ...doValid },
        });
      };
      render() {
        return (
          <div>
            <button onClick={this.clickHandler}>
              click me
            </button>
            <pre>
              {JSON.stringify(this.state, undefined, 2)}
            </pre>
          </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>

    【讨论】:

    • 请在 jsfiddle 上测试,有错误,无法正常工作
    • @tourtravel 啊,“不工作”,确实很有帮助。您是否收到错误或“不工作”是什么?是代码还是根本原因可以在其他地方找到?
    • 您可以在 jsfiddle 控制台日志中看到错误。无论如何:doValid is not defined
    【解决方案4】:

    你只需要这样做:

    this.setState({
      ...this.state,
      obj: {
       ...this.state.obj,
       [type]:v
      }
    })
    

    顺便说一句,我会这样做,而不是 for 循环:

    this.setState({
      ...this.state,
      obj: {
       ...this.state.obj,
       ...doValid
      }
    })
    

    仅供参考,在循环内部,this.state 正在返回先前的值,因为尚未应用挂起状态更新。请参阅this post 了解更多信息。

    这是一个适合你的小提琴:

    https://jsfiddle.net/09x2g5L6/

    【讨论】:

    • 没有改变JSFiddle 仍然是false 不是true
    • 仍然只是最后一个更改
    • 我测试了你的更新,但报错:doValid is not defined
    • @BhojendraRauniyar 不要打扰,我已经在答案中添加了一个。 OP 不知道范围,所以给了他关于范围的 ydkjs 书籍的参考。
    • @BhojendraRauniyar 和 @MHR 都回答在这里工作,但不明白一些事情,我需要将 doValid 传递给 handleValid 以获得我的验证器的回复,你们都删除了 handleValid ,现在我不知道我怎么能用你的代码做到这一点
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-08-03
    • 2017-04-24
    • 1970-01-01
    • 1970-01-01
    • 2017-04-13
    • 2013-12-31
    • 2017-01-14
    相关资源
    最近更新 更多