Почему в React componentWillReceiveprops срабатывает перед setState () в componentDidMount? - PullRequest
0 голосов
/ 30 августа 2018

Я уже некоторое время программирую с React, но никогда не сталкивался с этой досадной проблемой: в одном из моих компонентов componentWillReceiveProps срабатывает до запуска setState() в componentDidMount. Это вызывает несколько проблем в моем приложении.

У меня есть переменная this.props.flag, полученная от реквизита, которая будет сохранена в состоянии компонента:

    componentDidMount() {
        if (!_.isEmpty(this.props.flag)) {

            console.log('Flag Did:', this.props.flag);

            this.setState({
                flag: this.props.flag
            },
                () => doSomething()
            );
    }

В моем методе componentWillReceiveProps переменная this.state.flag будет заменена, просто если она пуста или если она отличается от значения this.props.flag (проверки выполняются с использованием библиотеки lodash):

componentWillReceiveProps(nextProps) {
    const { flag } = this.state;

    console.log('Flag Will:', !_.isEqual(flag, nextProps.flag), flag, nextProps.flag);

    if (!_.isEmpty(nextProps.flag) && !_.isEqual(flag, nextProps.flag)) {
            this.setState({
                flag: nextProps.flag,
            },
                () => doSomething()
            );
    }
}

Предположим, что пропеллер flag в этом случае всегда имеет одно и то же значение и что this.state.flag инициализируется undefined. Когда я проверяю журнал консоли, я вижу следующий результат:

Flag Did: true
Flag Will: true undefined true

Поэтому, когда код вводит componentWillReceiveProps, значение this.state.flag все еще равно undefined, это означает, что еще не было установлено setState в componentDidMount.

Это не соответствует жизненному циклу React или я что-то упустил? Как я могу избежать такого поведения?

Ответы [ 2 ]

0 голосов
/ 03 сентября 2018

Как предполагает пользователь JJJ, учитывая асинхронный характер setState, проверка if (!_.isEmpty(nextProps.flag) && !_.isEqual(flag, nextProps.flag)) в componentWillReceiveProps выполняется до того, как setState внутри componentDidMount выполнит flag: this.props.flag. Порядок операций:

  1. Код вводит componentDidMount.
  2. Код выполняет setState в componentDidMount (flag: this.props.flag еще не произошло).
  3. Код выхода componentDidMount, setState в componentDidMount есть все еще находится в процессе выполнения (flag: this.props.flag еще не произошло).
  4. Компонент получает новые реквизиты, поэтому входит componentWillReceiveProps.
  5. Заявление if (!_.isEmpty(nextProps.flag) && !_.isEqual(flag, nextProps.flag)) в componentWillReceiveProps выполнено (this.state.flag по-прежнему не определено).
  6. Код заканчивает выполнение setState внутри componentDidMount и устанавливает flag: this.props.flag и выполняет doSomething().
  7. Код заканчивает выполнение setState внутри componentWillMount и устанавливает flag: nextProps.flag и выполняет doSomething().

Учитывая асинхронную природу setState, 6 и 7 могут выполняться параллельно, и поэтому мы не знаем, кто из них завершит свое выполнение первым. DoSomething() в этом случае потенциально вызывается как минимум 2 раза, тогда как вместо этого он должен вызываться один раз

Чтобы решить эти проблемы, я изменил свой код следующим образом:

componentWillReceiveProps(nextProps)  {

if (!_.isEmpty(nextProps.flag) && !_.isEqual(this.props.flag, nextProps.flag)) {
        this.setState({
            flag: nextProps.flag,
        },
            () => doSomething()
        );
    }
}

Таким образом, я сравниваю новую версию (nextProps) со старой версией (this.props) реквизита, не дожидаясь сохранения значения flag в состоянии компонента.

0 голосов
/ 30 августа 2018

ComponentWillReceiveProps() будет вызываться в каждом жизненном цикле обновления, вызванном изменениями реквизита (повторная визуализация родительского компонента). Поскольку Javascript является синхронным, вы можете иногда проверять реквизиты, чтобы сохранить сбои приложения. Я не совсем понял контекст вашего приложения, но вы можете сделать следующее:

componentWillReceiveProps(nextProps) {
    const { flag } = this.state;

    if(!flag){
      return;, 
    }

    console.log('Flag Will:', !_.isEqual(flag, nextProps.flag), flag, nextProps.flag);

    if (!_.isEmpty(nextProps.flag) && !_.isEqual(flag, nextProps.flag)) {
            this.setState({
                flag: nextProps.flag,
            },
                () => doSomething()
            );
    }
}

Вы можете вернуться, если состояние не определено. Он будет вызван снова после повторного рендеринга. Но это может быть не вариант использования.

В любом случае вам стоит заглянуть в это :

Но я могу вспомнить хотя бы 1 (возможно, теоретический) сценарий, в котором порядок будет обратным:

Компонент получает реквизит и начинает рендеринг. Пока компонент рендеринг, но еще не закончил рендеринг, компонент получает новый реквизит. componentWillReceiveProps () запускается, (но componentDidMount еще не уволили) Ведь дети и сам компонент есть После завершения рендеринга компонентDidMount () сработает. Так componentDidMount () не является хорошим местом для инициализации переменные-компоненты, такие как ваш {foo: 'bar'}. componentWillMount () было бы лучшим событием жизненного цикла. Тем не менее, я бы не рекомендовал любое использование общекомпонентных переменных внутри реагирующих компонентов, и придерживаться принципы проектирования:

все переменные компонента должны находиться либо в состоянии, либо в подпорках (и быть неизменяемый) все остальные переменные связаны методом жизненного цикла (и не более того)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...