ReactJS: 警告:setState(...): 不能在现有状态转换期间更新

267

我正在尝试重构我的渲染视图中的以下代码:

<Button href="#" active={!this.state.singleJourney} onClick={this.handleButtonChange.bind(this,false)} >Retour</Button>

将绑定放在构造函数中的版本。原因是在渲染视图中使用bind会导致性能问题,尤其是在低端移动电话上。

我创建了以下代码,但是我不断收到以下错误消息(大量错误)。看起来应用程序进入了一个循环:

Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.

以下是我使用的代码:

var React = require('react');
var ButtonGroup = require('react-bootstrap/lib/ButtonGroup');
var Button = require('react-bootstrap/lib/Button');
var Form = require('react-bootstrap/lib/Form');
var FormGroup = require('react-bootstrap/lib/FormGroup');
var Well = require('react-bootstrap/lib/Well');

export default class Search extends React.Component {

    constructor() {
        super();

        this.state = {
            singleJourney: false
        };

        this.handleButtonChange = this.handleButtonChange.bind(this);
    }

    handleButtonChange(value) {
        this.setState({
            singleJourney: value
        });
    }

    render() {

        return (
            <Form>

                <Well style={wellStyle}>

                    <FormGroup className="text-center">

                        <ButtonGroup>
                            <Button href="#" active={!this.state.singleJourney} onClick={this.handleButtonChange(false)} >Retour</Button>
                            <Button href="#" active={this.state.singleJourney} onClick={this.handleButtonChange(true)} >Single Journey</Button>
                        </ButtonGroup>
                    </FormGroup>

                </Well>

            </Form>
        );
    }
}

module.exports = Search;

我也遇到了同样的问题,但现在是与onClick()有关:https://stackoverflow.com/questions/57079814/react-cannot-update-during-an-existing-state-transition-such-as-within-render - Saibamen
这个问题与onClick()无关:https://stackoverflow.com/questions/57079814/react-cannot-update-during-an-existing-state-transition-such-as-within-render - Saibamen
11个回答

383

看起来你在render方法中意外地调用了handleButtonChange方法,你可能想使用onClick={() => this.handleButtonChange(false)}代替。

如果你不想在onClick处理程序中创建lambda,则需要有两个绑定的方法,每个参数一个。

constructor中:

this.handleButtonChangeRetour = this.handleButtonChange.bind(this, true);
this.handleButtonChangeSingle = this.handleButtonChange.bind(this, false);

render方法中:

<Button href="#" active={!this.state.singleJourney} onClick={this.handleButtonChangeSingle} >Retour</Button>
<Button href="#" active={this.state.singleJourney} onClick={this.handleButtonChangeRetour}>Single Journey</Button>

2
也许发生的情况是当我设置活动状态时,它会触发 onClick,导致循环。有没有办法在不触发 onClick 的情况下设置活动状态? - user3611459
2
为什么Lambda是解决方案?背后的概念是什么?我看不到它。 - Vicens Fayos
42
"onClick={this.handleButtonChange(false)}" 和 "onClick={() => this.handleButtonChange(false)}" 之间的主要区别在于前者是错误的,它只是立即调用 handleButtonChange 方法并将其返回值(未定义)分配给 onClick 处理程序-因此什么也不会发生。后者实际上将一个方法分配给了 onClick - 该方法调用 handleButtonChange。 - Vladimir Rovensky
我有一位React的高级主管会不时地帮助我。他说我应该避免使用匿名函数,因为会影响性能。他说匿名函数会分配一些无法被销毁的内存之类的东西。我想知道你对此的看法,谢谢。 - Becario Senior
有些情况下,匿名函数会影响性能,最常见的情况是当你将匿名函数作为一个属性(例如在示例中的Button)传递给组件,并且该组件定义了shouldComponentUpdate 方法来使用 === 比较属性(例如 PureComponent)。传递匿名函数会阻止此优化的工作,因为在每次呈现时都会创建函数的新实例,因此 shouldComponentUpdate 中的 === 始终返回 false,导致子组件必须重新渲染。在这种情况下,最好像我的答案的后半部分那样使用命名方法。 - Vladimir Rovensky
显示剩余4条评论

19

我提供一个通用的例子以便更好地理解,在下面的代码中

render(){
    return(
      <div>

        <h3>Simple Counter</h3>
        <Counter
          value={this.props.counter}
          onIncrement={this.props.increment()} <------ calling the function
          onDecrement={this.props.decrement()} <-----------
          onIncrementAsync={this.props.incrementAsync()} />
      </div>
    )
  }

在提供props时,我直接调用了该函数,这将导致无限循环执行并引发错误,如果移除函数调用,则一切正常工作。

render(){
    return(
      <div>

        <h3>Simple Counter</h3>
        <Counter
          value={this.props.counter}
          onIncrement={this.props.increment} <------ function call removed
          onDecrement={this.props.decrement} <-----------
          onIncrementAsync={this.props.incrementAsync} />
      </div>
    )
  }

那时候我得到了无限循环异常,难怪呢。我真傻。之后,我开始使用构造函数上的bind按钮和箭头函数()=> - Luiey

15

当你调用onClick={this.handleButton()时,通常会发生这种情况 - 注意这里是()而不是:

onClick={this.handleButton} - 注意这里我们在初始化函数时没有调用该函数


8
问题在这里:onClick={this.handleButtonChange(false)} 当您将`this.handleButtonChange(false)`传递给`onClick`时,实际上是使用`value=false`调用该函数,并将`onClick`设置为函数的返回值,该值为未定义。同时调用`this.handleButtonChange(false)`然后调用`this.setState()`,触发重新渲染,导致无限重复渲染。 解决方案是使用lambda函数传递函数:onClick={() => this.handleButtonChange(false)}。这里,您正在将`onClick`设置为相等于单击按钮时将调用`handleButtonChange(false)`的函数。
下面的示例可能有所帮助:
function handleButtonChange(value){
  console.log("State updated!")
}

console.log(handleButtonChange(false))
//output: State updated!
//output: undefined

console.log(() => handleButtonChange(false))
//output: ()=>{handleButtonChange(false);}

4
如果您尝试在recompose中向处理程序添加参数,请确保在处理程序中正确定义参数。它本质上是一种柯里化函数,因此您要确保需要正确数量的参数。 该页面有一个使用带有处理程序的参数的好示例。 示例(来自链接):
withHandlers({
  handleClick: props => (value1, value2) => event => {
    console.log(event)
    alert(value1 + ' was clicked!')
    props.doSomething(value2)
  },
})

为了您的子组件高阶函数,以及在父组件中使用。
class MyComponent extends Component {
  static propTypes = {
    handleClick: PropTypes.func, 
  }
  render () {
    const {handleClick} = this.props
    return (
      <div onClick={handleClick(value1, value2)} />
    )
  }
}

这样做可以避免在处理程序上写一个匿名函数来修补由于未提供足够的参数名称而引起的问题。

3
这个问题当然是在渲染带有 onClick 方法的按钮时出现了 this 绑定错误。解决方法是在渲染时使用箭头函数调用操作处理程序。就像这样:onClick={ () => this.handleButtonChange(false) }

好主意! :) - Oliamster

2

render()调用中进行的任何状态更改都会发出相同的警告。

一个难以找到的案例示例: 当基于状态数据渲染多选GUI组件时,如果状态没有要显示的内容,则对resetOptions()的调用被认为是该组件的状态更改。

明显的解决方法是在componentDidUpdate()而不是render()中执行resetOptions()


2

来自React文档传递参数给事件处理函数

<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

1

onClick函数必须通过返回handleButtonChange()方法的函数进行传递。否则它将自动运行,导致错误/警告。使用以下方法解决此问题。

onClick={() => this.handleButtonChange(false)}


1

当我调用时,遇到了相同的错误。

this.handleClick = this.handleClick.bind(this);

在我的构造函数中,当handleClick不存在时(我已经将其删除,并意外地在我的构造函数中留下了“this”绑定语句)。
解决方法= 删除“this”绑定语句。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接