Что происходит при многократном использовании this.setState в компоненте React?

102

Я хотел проверить, что происходит, когда вы используете 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')
);

Как вы увидите, предупреждение с надписью «рендеринг» появляется при каждом рендеринге.

У вас есть объяснение, почему это сработало правильно?

Александр
источник
2
React довольно умен и будет выполнять повторный рендеринг только при изменении состояния, необходимого для рендеринга. В вашем методе рендеринга вы только используете this.state.alex- что произойдет, если вы добавите элемент, который также зависит от this.state.value?
Мартин Ведвич
1
@MartinWedvich Он сломается, если я от него зависим. Для этого у вас есть метод getInitialState - чтобы установить значения по умолчанию, чтобы ваше приложение не сломалось.
alexunder
1
связанные и полезные: stackoverflow.com/questions/48563650/…
andydavies

Ответы:

118

Реагировать на обновления состояния пакетов, которые происходят в обработчиках событий и методах жизненного цикла. Таким образом, если вы обновляете состояние несколько раз в <div onClick />обработчике, React будет ждать завершения обработки события перед повторным рендерингом.

Чтобы было ясно, это работает только в управляемых React синтетических обработчиках событий и методах жизненного цикла. setTimeoutНапример, обновления состояния не пакетируются в AJAX и обработчиках событий.

Крис Годро
источник
1
Спасибо, это имеет смысл, есть идеи, как это делается за кулисами?
alexunder
13
Поскольку React полностью контролирует синтетические обработчики событий (например <div onClick />) и методы жизненного цикла компонентов, он знает, что может безопасно изменить поведение setStateна время вызова пакетных обновлений состояния и дождаться сброса. setTimeoutНапротив, в AJAX или обработчике React не имеет возможности узнать, когда наш обработчик завершил выполнение. Технически React ставит обновления состояния в очередь в обоих случаях, но немедленно сбрасывается за пределы обработчика, управляемого React.
Крис Годро
1
@neurosnap Я не думаю, что в документации подробно говорится об этом. Это абстрактно упоминается в разделе « Состояние и жизненный цикл» и в документации по setState . См. Код ReactDefaultBatchingStrategy , который в настоящее время является единственной официальной стратегией пакетной обработки .
Крис Годро
2
@BenjaminToueg Я считаю, что вы можете использовать ReactDOM.unstable_batchedUpdates(function)для пакетной обработки setStateвне обработчиков событий React. Как следует из названия, вы аннулируете свою гарантию.
Крис Годро
1
Всегда хорошо сослаться на этого парня twitter.com/dan_abramov/status/887963264335872000?lang=en
Герцель Гиннесс
35

Метод setState () не обновляет сразу состояние компонента, он просто помещает обновление в очередь для последующей обработки. React может объединять несколько запросов на обновление, чтобы сделать рендеринг более эффективным. В связи с этим следует соблюдать особые меры предосторожности при попытке обновить состояние на основе предыдущего состояния компонента.

Например, следующий код увеличит атрибут значения состояния только на 1, даже если он был вызван 4 раза:

 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]) может принимать функцию обновления в качестве своего первого аргумента для обновления состояния на основе предыдущего состояния и свойств. Возвращаемое значение функции обновления будет неглубоко объединено с предыдущим состоянием компонента. Метод обновляет состояние асинхронно, поэтому существует обратный вызов параметра, который будет вызываться после полного завершения обновления состояния.

Пример:

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>
     }
  }
Biboswan
источник
Ах, я не знал насчет этого setState(updater,[callback]), это как менее многословное Object.assignспасибо за это!
AJ Venturella
1
@Biboswan, привет, я новичок в React и хотел спросить вас, правда ли, что все четыре setState внутри метода CompountDidMount объединены, и ТОЛЬКО ПОСЛЕДНИЙ setState будет выполнен, но остальные 3 будут проигнорированы?
Диккенс