[Подробно реагировать] механизм выполнения setState

React.js

1. Несколько проблем, часто встречающихся при разработке

Следующие проблемы представляют собой сценарии, с которыми мы часто сталкиваемся в реальной разработке. Давайте восстановим их с помощью нескольких простых примеров кода.

1. Является ли setState синхронным или асинхронным, почему я не могу сразу получить результат обновления, но иногда могу?

1.1 Хуки-функции и синтетические события ReactsetState

Теперь есть два компонента

  componentDidMount() {
    console.log('parent componentDidMount');
  }

  render() {
    return (
      <div>
        <SetState2></SetState2>
        <SetState></SetState>
      </div>
    );
  }

Поместите тот же код внутри компонента и поместите его вSetstate1серединаcomponentDidMountВ разделе кода размещена задержка синхронизации, время задержки печати:

  componentWillUpdate() {
    console.log('componentWillUpdate');
  }

  componentDidUpdate() {
    console.log('componentDidUpdate');
  }

  componentDidMount() {
    console.log('SetState调用setState');
    this.setState({
      index: this.state.index + 1
    })
    console.log('state', this.state.index);
    
    console.log('SetState调用setState');
    this.setState({
      index: this.state.index + 1
    })
    console.log('state', this.state.index);
  }

Результат выполнения следующий:

image

инструкция:

  • 1. звонокsetStateне будет обновляться сразу
  • 2. Все компоненты используют один и тот же механизм обновления, когда все компонентыdidmountПосле родительского компонентаdidmountИ выполнить обновление
  • 3. При обновлении обновления каждого компонента будут объединены, и каждый компонент запустит жизненный цикл обновления только один раз.

1.2 Асинхронные функции и нативные событияsetstate?

существуетsetTimeoutвызыватьsetState(Пример такой же, как выполнение в браузере собственных событий и обратных вызовов интерфейса)

  componentDidMount() {
    setTimeout(() => {
      console.log('调用setState');
      this.setState({
        index: this.state.index + 1
      })
      console.log('state', this.state.index);
      console.log('调用setState');
      this.setState({
        index: this.state.index + 1
      })
      console.log('state', this.state.index);
    }, 0);
  }

Результаты:

image

инструкция:

  • 1. В родительском компонентеdidmountпосле казни
  • 2. позвонитьsetStateОбновление синхронизации

2. Почему иногда два раза подрядsetStateРаботает только один раз?

Выполните следующие коды соответственно:

  componentDidMount() {
    this.setState({ index: this.state.index + 1 }, () => {
      console.log(this.state.index);
    })
    this.setState({ index: this.state.index + 1 }, () => {
      console.log(this.state.index);
    })
  }
  componentDidMount() {
    this.setState((preState) => ({ index: preState.index + 1 }), () => {
      console.log(this.state.index);
    })
    this.setState(preState => ({ index: preState.index + 1 }), () => {
      console.log(this.state.index);
    })
  }

Результаты:

1
1
2
2

инструкция:

  • 1. Передайте объект напрямуюsetstateбудут объединены в один
  • 2. Используйте проход функцииstateне будет объединен

Два .SetState Исполнение

Поскольку исходный код более сложный, он не будет размещен здесь, если вам интересно, вы можете перейти наgithubначальствоcloneОдна копия, а затем просмотрите ее в соответствии с приведенной ниже блок-схемой.

1. Блок-схема

setState

Картинка не четкая, можно нажать

  • partialState:setStateПервый переданный параметр, объект или функция
  • _pendingStateQueue: текущий компонент ожидает выполнения обновленияstateочередь
  • isBatchingUpdates: реакция используется, чтобы определить, находится ли он в настоящее время в состоянии пакетного обновления, общего для всех компонентов.
  • dirtyComponent: Очереди всех компонентов, находящихся в настоящее время в состоянии ожидания обновления.
  • transcation: механизм транзакций React, оборачивающий n методов вокруг метода, вызываемого транзакцией.waperобъект и выполнить его один раз:waper.init, вызываемый метод,waper.close
  • FLUSH_BATCHED_UPDATES: используется для выполнения обновленияwaper,только одинcloseметод

2. Процесс исполнения

Согласно текстовому описанию приведенной выше блок-схемы, ее можно условно разделить на следующие этапы:

  • 1. Передайте в setStatepartialStateПараметры хранятся в промежуточной очереди состояния текущего экземпляра компонента.
  • 2. Определите, находится ли текущий React в состоянии пакетного обновления, и если да, добавьте текущий компонент в очередь компонентов для обновления.
  • 3. Если он не находится в состоянии пакетного обновления, установите для флага состояния пакетного обновления значение true и используйте транзакцию для повторного вызова предыдущего метода, чтобы убедиться, что текущий компонент добавлен в очередь компонентов для обновления.
  • 4. Вызвать транзакциюwaperметод, пройдите по очереди компонентов, которые необходимо обновить, и выполните обновления последовательно.
  • 5. Жизненный цикл исполненияcomponentWillReceiveProps.
  • 6. Временно сохранить состояние компонента в очередиstateСлияние, получение объекта конечного состояния для обновления и очистка очереди.
  • 7. Жизненный цикл исполненияcomponentShouldUpdate, в соответствии с возвращаемым значением, чтобы определить, следует ли продолжать обновление.
  • 8. Жизненный цикл исполненияcomponentWillUpdate.
  • 9. Выполните настоящее обновление,render.
  • 10. Жизненный цикл выполненияcomponentDidUpdate.

3. Резюме

1. В хуковых функциях и синтетических событиях:

существуетreactв жизненном цикле и синтетических событиях,reactвсе еще в своем механизме обновления, в это времяisBranchUpdateправда.

В соответствии с описанным выше процессом, независимо от того, сколько раз вы звонитеsetState, не будет выполнять обновление, но обновитstateдепозит_pendingStateQueue, сохраните компонент, который нужно обновить, вdirtyComponent.

При выполнении последнего механизма обновления, на примере жизненного цикла, все компоненты, то есть компонент верхнего уровняdidmountпозжеisBranchUpdateУстановите значение «ложь». В это время накопленный ранееsetState.

2. В асинхронных функциях и нативных событиях

С точки зрения исполнительного механизма,setStateне является асинхронным как таковым, но при вызовеsetStateкогда, еслиreactВ процессе обновления текущее обновление будет временно сохранено и будет выполнено после выполнения последнего обновления, что создает иллюзию асинхронности.

В жизненном цикле, согласно асинхронному механизму JS, асинхронная функция будет временно храниться, и она будет выполняться после выполнения всех синхронных кодов, в это время был выполнен последний процесс обновления.isBranchUpdateустановлено значение false в соответствии с описанным выше процессом, затем снова вызовитеsetStateВы можете выполнить обновление немедленно и получить результат обновления.

3.partialStateМеханизм слияния

Давайте посмотрим на процесс_processPendingStateкод, эта функция используется для слиянияstateВременная очередь и, наконец, вернуть объединенныйstate.


  _processPendingState: function (props, context) {
    var inst = this._instance;
    var queue = this._pendingStateQueue;
    var replace = this._pendingReplaceState;
    this._pendingReplaceState = false;
    this._pendingStateQueue = null;

    if (!queue) {
      return inst.state;
    }

    if (replace && queue.length === 1) {
      return queue[0];
    }

    var nextState = _assign({}, replace ? queue[0] : inst.state);
    for (var i = replace ? 1 : 0; i < queue.length; i++) {
      var partial = queue[i];
      _assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
    }

    return nextState;
  },

Нам просто нужно сосредоточиться на следующем коде:

 _assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);

Если объект передается, он, очевидно, будет объединен один раз:

Object.assign(
  nextState,
  {index: state.index+ 1},
  {index: state.index+ 1}
)

Если передается функция, параметр preState функции является результатом предыдущего слияния, поэтому результат вычисления является точным.

4.componentDidMountперечислитьsetstate

В componentDidMount() вы можете немедленно вызвать setState(). Это вызовет дополнительный рендеринг, но это произойдет до того, как браузер обновит экран. Это гарантирует, что пользователь не увидит промежуточное состояние, даже если в этом случае функция render() будет вызываться дважды. Используйте этот режим с осторожностью, так как он часто вызывает проблемы с производительностью. В большинстве случаев вместо этого вы можете использовать начальное состояние присваивания в конструкторе(). Однако бывают ситуации, когда это необходимо, например, для модальных окон и всплывающих подсказок. На этом этапе вам нужно измерить эти узлы DOM, прежде чем отображать что-то, что зависит от размера или положения.

Выше приведено описание официального документа, не рекомендуется использоватьcomponentDidMountпозвонить напрямуюsetState, согласно приведенному выше анализу:componentDidMountсам в обновлении, звоним сноваsetState, сделаю это снова в будущемrender, вызывая ненужные потери производительности, в большинстве случаев это можно сделать, установив начальное значение.

Конечно вcomponentDidMountМы можем вызвать интерфейс и изменить его в обратном вызовеstate, это правильный подход.

Когда начальное значение состояния зависит от атрибута dom, вcomponentDidMountсерединаsetStateнеизбежно.

5.componentWillUpdate componentDidUpdate

Эти два жизненных цикла нельзя назватьsetState.

Это легко найти из приведенной выше блок-схемы, в которой они вызываютsetStateЭто вызовет бесконечный цикл и приведет к сбою программы.

6. Рекомендуемое использование

вызовsetStateПри использовании функции passstateзначение, получить последнее обновленное значение в функции обратного вызоваstate.