在 React 组件中多次使用 this.setState 时会发生什么?

137

我想检查一下当你多次使用this.setState时会发生什么(为了讨论,假设使用2次)。我以为组件会被渲染两次,但实际上只渲染了一次。我还期望第二次调用setState可能会覆盖第一次,但是你猜对了——它运行得很好。

链接到JSfiddle

var Hello = React.createClass({
  render: function() {
    return (
      <div>
        <div>Hello {this.props.name}</div>
        <CheckBox />
      </div>
    );
  }
});

var CheckBox = React.createClass({
  getInitialState: function() {
    return {
      alex: 0
    };
  },

  handleChange: function(event) {
    this.setState({
      value: event.target.value
    });
    this.setState({
      alex: 5
    });
  },

  render: function() {
    alert('render');
    return (
      <div>
        <label htmlFor="alex">Alex</label>
        <input type="checkbox" onChange={this.handleChange} name="alex" />
        <div>{this.state.alex}</div>
      </div>
    );
  }
});

ReactDOM.render(
  <Hello name="World" />,
  document.getElementById('container')
);

正如您所看到的,在每次渲染时都会弹出一个显示“render”的警告。

您有为什么它能正常工作的解释吗?


2
React非常聪明,只有在需要重新渲染的状态改变时才会重新渲染。在您的渲染方法中,您只使用了this.state.alex - 如果您添加一个还依赖于this.state.value的元素会发生什么? - Martin Wedvich
1
它将会出错,如果我依赖于它。为此,你可以使用“getInitialState”方法-设置默认值,这样你的应用程序就不会崩溃。 - alexunder
1
相关且有用的:https://dev59.com/vFYM5IYBdhLWcg3wpRXa#48610973 - andydavies
2个回答

158

React通过批处理事件处理程序和生命周期方法中发生的状态更新。因此,如果您在<div onClick />处理程序中多次更新状态,则React将等待事件处理完成后再重新渲染。

需要明确的是,这仅适用于React控制的合成事件处理程序和生命周期方法。例如,在AJAX和setTimeout事件处理程序中不会批量更新状态。

更新

随着React v18.0的发布,默认情况下也会对非React事件(如promises、setTimeout等)中的状态更新进行批处理。

参考 - https://reactjs.org/blog/2022/03/29/react-v18.html#new-feature-automatic-batching


2
谢谢,很有道理。你知道它在幕后是如何完成的吗? - alexunder
19
由于React完全控制合成事件处理程序(例如<div onClick />)和组件生命周期方法,它知道可以在批量状态更新的调用期间安全更改setState的行为并等待刷新。相比之下,在AJAX或setTimeout处理程序中,React无法知道我们的处理程序何时完成执行。从技术上讲,React在这两种情况下都会将状态更新排队,但在React不受控制的处理程序之外会立即刷新。 - Chris Gaudreau
2
@neurosnap 我认为文档没有详细说明这个问题。它在状态和生命周期setState文档中抽象地提到了。请参阅ReactDefaultBatchingStrategy的代码,这是目前唯一官方的批处理策略。 - Chris Gaudreau
2
@BenjaminToueg 我相信你可以使用 ReactDOM.unstable_batchedUpdates(function) 来批量处理 setState,而不需要在 React 事件处理程序中进行。正如其名称所示,这样做会使你的保修失效。 - Chris Gaudreau
3
这个人很值得参考:https://twitter.com/dan_abramov/status/887963264335872000?lang=en - Hertzel Guinness
显示剩余4条评论

61

setState()方法不会立即更新组件的状态,它只是将更新放入队列中以供稍后处理。React可能会将多个更新请求批处理在一起以使呈现更有效率。因此,当您尝试基于组件的先前状态更新状态时,必须采取特殊预防措施。

例如,即使调用了4次以下代码,状态值属性仅增加1:

 class Counter extends React.Component{
   constructor(props){
     super(props)
    //initial state set up
     this.state = {value:0}
   }
   componentDidMount(){
    //updating state
    this.setState({value:this.state.value+1})
    this.setState({value:this.state.value+1})
    this.setState({value:this.state.value+1})
    this.setState({value:this.state.value+1})
   }
   render(){
    return <div>Message:{this.state.value}</div>
   }
}

为了在更新状态后使用该状态,请将所有逻辑放在回调参数中:

//this.state.count is originally 0
this.setState({count:42}, () => {
  console.log(this.state.count)
//outputs 42
})

setState(updater,[callback])方法可以接受一个updater函数作为其第一个参数,根据先前的状态和属性更新状态。 updater函数的返回值将与之前的组件状态进行浅合并。该方法异步地更新状态,因此有一个选项回调函数,一旦状态完全更新完毕,就会被调用。

示例:

this.setState((prevState, props) => { 
return {attribute:"value"}
})

这是一个根据先前状态更新状态的示例:

    class Counter extends React.Component{
      constructor(props) {
        super(props)
    //initial state set up
        this.state = {message:"initial message"}
    }
      componentDidMount() {
    //updating state
        this.setState((prevState, props) => {
          return {message: prevState.message + '!'}
        })
     }
     render(){
       return <div>Message:{this.state.message}</div>
     }
  }

1
啊,我不知道setState(updater,[callback]),它就像是一个更简洁的Object.assign,谢谢你! - AJ Venturella
3
你好,@Biboswan。我是React的新手,想问一下你,CompountDidMount方法中的所有四个setStates是否会合并,只有最后一个setState将被执行,其余三个将被忽略,这种说法是否正确? - Dickens
对我来说,关键是声明 previousState 参数并在更新函数中使用它。 - Saeed Neamati
1
有任何想法关于React如何决定是否批量更新或立即应用更新吗?我说的是它在代码层面内部是如何实现的。 - darKnight
1
@darKnight,这主要基于事件循环和16ms进入1帧的概念(每秒60帧)。为了进行最佳讨论,我建议您在discord的reactiflux服务器的内部频道中提问。 - Biboswan

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