警告:setState(…):无法在现有状态转换期间更新

IT技术 javascript reactjs
2021-05-06 22:26:10

我正在开发一个简单的“待办事项”react应用程序(React.js 的新手)。我已将项目添加到列表中,但删除项目会引发问题。在我的父react组件中,我有以下代码:

import ToDoEntries from './to_do_entries.jsx';

class ToDoList extends React.Component {
  constructor(props) {
    super(props);
    this.state = { list: [] }
    this.add = this.addItem.bind(this);
    this.removeItem = this.removeItem.bind(this);
  }

  addItem(e) { //removed to avoid tl:dr }

  render() {
    return(
      <form onSubmit={this.add}>
        <input placeholder='Enter item' type='text' ref={(el) => {this._input = el;} }/>
        <button>Add</button>
      </form>

      <ToDoEntries entries={this.state.list}
        removeCallback={this.removeItem}
      />
    );
  }

}

我的to_do_entries.jsx组件:

class ToDoEntries extends React.Component {
  constructor(props) {
    super(props);
  }

  renderItems() {
    const { entries, removeCallback } = this.props;

    function createTasks(item) {
      return <li key={item.key}>{item.text}</li>
    }

    var listItems = entries.map(function(item) {
      return(<li onClick={removeCallback} key={item.key}>{item.text}</li>)
    })

    return listItems;
  }

  render() {
    var todoEntries = this.renderItems();

    return(
      <ul>
        {todoEntries}
      </ul>
    );
  }
}

export default ToDoEntries;

运行此代码带来:

警告:setState(…):无法在现有状态转换期间更新

问题:

为什么to_do_entries.jsx's render 在添加项目时立即执行回调,即:

var listItems = entries.map(function(item) {
  return(<li onClick={removeCallback(id)} key={item.key}>{item.text}</li>)
})

但是,添加.bind(null, id)到 removeCallback 即。<li onClick={removeCallback.bind(null, id)} />才不是?

3个回答

问题出在这部分:

onClick={removeCallback(id)}

我们需要将一个函数传递给 onClick,而不是值。当我们使用()with functionName 时,这意味着您正在调用该方法并将其结果分配给onClick如果您在 removeCallback 中这样做,这将创建一个无限循环setState,因为这个循环:

render ->  removeCallback()  ->  setState ->
  ^                                         |
  |                                         |
  |                                         |
   -----------------------------------------

这就是您收到错误的原因。

检查片段之间的区别abc and abc()

function abc(){
   return 'Hello';
}

console.log('without () = ', abc);     //will return the function
 
console.log('with () = ', abc());      //will return the function result (value)

为什么它与onClick={removeCallback.bind(null, id)}?

因为 bind 会创建一个新函数,并将该函数分配给 click 事件,所以removeCallback当你点击任何非自动的项目时,这里会被调用。

根据MDN 文档

bind() 函数创建一个新的绑定函数 (BF)。BF 是一个奇异的函数对象(来自 ECMAScript 2015 的术语),它包装了原始函数对象。调用 BF 通常会导致执行其包装函数。

检查React DOC:在 JSX 中处理事件

检查此答案以获取有关绑定的更多详细信息:使用 JavaScript 'bind' 方法

我建议不要这样做,并使用与我为您编写的示例类似的方法。呈现绑定到状态的待办事项列表,然后将相关信息传递回您的父组件以删除该项目。在这种情况下,我使用 todo 的索引来拼接数组,以便将其删除。

当每个待办事项<li>呈现时,您当前的 onClick 会立即被调用,因为它只是导致问题的函数调用。.bind解决了这个问题,因为当您单击元素时它会创建一个新函数,这就是该函数不会立即调用的原因。

然而,这通常被认为是不好的做法,因为每次组件都会一次又一次地创建这个函数。将此乘以屏幕上的待办事项数量,您将失去性能。这是一个小问题,但我的例子展示了如何解决这个问题。 https://codepen.io/w7sang/pen/VWNLJp

// App
class App extends React.Component{
  constructor(props) {
    super(props);
    this.state = { list: [] }
    this.add = this.addItem.bind(this);
    this.removeItem = this.removeItem.bind(this);
  }
  addItem(e) { 
    e.preventDefault();
    this.setState({
      list: [ 
        ...this.state.list, 
        {
          key: Math.random(1,10000),
          text: this._input.value
        }
      ]
    })
  }
  removeItem(payload){
    this.setState({
      list: [ 
        ...this.state.list.slice(0, payload.index),
        ...this.state.list.slice(payload.index + 1)
      ]
    })
  }
  render() {
    return(
      <div>
        <form onSubmit={this.add}>
          <input placeholder='Enter item' type='text' ref={(el) => {this._input = el;} }/>
          <button>Add</button>
        </form>
        <ToDoEntries entries={this.state.list} removeItem={this.removeItem} />
      </div>
    );
  }
}

// TodoEntries [STATELESS]
const ToDoEntries = ({ entries, removeItem } ) => {
  return(
    <ul>
      { entries.map((item, index) => {
        return(<Todo key={item.key} index={index} item={item} removeItem={removeItem} />)
      }) }
    </ul>
  );
}

// Todo
class Todo extends React.Component {
  constructor(props){
    super(props);
    this.state = {};
    this.remove = this.remove.bind(this);
  }
  remove() {
    const { index, removeItem } = this.props;
    removeItem({
      index
    });
  }
  render() {
    return <li onClick={this.remove}>{this.props.item.text}</li>
  }
}

ReactDOM.render(<App />,document.getElementById('app'));
<div id="app"></div>

为什么 to_do_entries.jsx 的 render 会立即执行回调?

好吧,当您对待办事项列表进行映射时,每个都<li/>在调用该removeCallback函数而不是将其分配给onClick.

所以当前代码

<li onClick={removeCallback(id)} </li>

相当于:

var result = removeCallback(id);
<li onClick={result} </li>

您已经正确地指出使用bind会起作用。这是由于它在这些情况下非常有用的行为。

有关更多信息请参阅 mdn 文档,但我将在此处引用重要部分:

bind... 创建并返回一个新函数,该函数在调用时...

在您的情况下,当使用 bind 并将其提供给您时,onClick您将创建一个新函数,该函数将在实际触发 click 事件时调用,而不是在呈现元素时调用。

另一种看待方式removeCallback.bind(null, id)是这样的:

var newFunc = () => {
  return removeCallback(id);
}
<li onClick={newFunc} </li>