Анализ и план обновления новых функций жизненного цикла версии React v16.3

внешний интерфейс React.js Promise Открытый исходный код

Месяц назад React официально выпустил версию V16.3. В этом обновлении, кроме фронта, время называется теплоНовый контекстный APIКроме того, недавно введенные две функции жизненного циклаgetDerivedStateFromProps,getSnapshotBeforeUpdateи три функции жизненного цикла, которые будут удалены в будущем выпуске v17.0.componentWillMount,componentWillReceiveProps,componentWillUpdateМы также уже стоим уделить время, чтобы исследовать причины позади него и обновления программ в конкретных проектах.

componentWillMount

Первый результат пустой экран нет данных

В приложениях React многие разработчики помещают код части запроса данных, чтобы избежать белого экрана, вызванного отсутствием получения асинхронных данных при первом отображении страницы.componentWillMount, есть надежда, что белого экрана можно избежать и время отправки асинхронного запроса может быть раньше. но по фактуcomponentWillMountПосле выполнения первый рендеринг уже начался, поэтому если вcomponentWillMountЕсли асинхронные данные не были получены во время выполнения, страница по-прежнему будет находиться в состоянии без асинхронных данных при первом отображении. Другими словами, компонент всегда будет в состоянии без асинхронных данных при первом рендеринге, поэтому независимо от того, куда отправляется запрос данных, прямого решения этой проблемы не существует. И оОтправить запрос данных заранее, чиновник также рекомендует размещать код части запроса данных в компонентеconstructorвместоcomponentWillMount.

другой общийcomponentWillMountВариант использования для выборки данных — это серверный рендеринг, потому что при серверном рендерингеcomponentDidMountне будет называться. Для этой проблемы автор предлагает два решения. Первое простое решение — поместить все запросы данных вcomponentDidMount, то есть асинхронные данные запрашиваются только на стороне клиента. Это позволяет избежать двойного запроса одних и тех же данных как на стороне сервера, так и на стороне клиента (componentWillMountОн также будет вызываться во время рендеринга на стороне клиента), но очевидным недостатком является то, что все данные, необходимые для рендеринга страницы, не могут быть получены во время рендеринга на стороне сервера, поэтому, если нам нужно убедиться, что HTML, возвращаемый на стороне сервера, что в итоге видит пользователь. В случае с HTML мы можем извлечь логику сбора данных каждой страницы отдельно, а затем по очереди соответствовать соответствующей странице, найти соответствующий запрос данных на стороне сервера по маршруту текущего страницу и используйте связанный промис для рендеринга окончательного. Данные помещаются в хранилище избыточности или другие инструменты управления данными перед страницей, так что HTML, возвращаемый сервером, является результатом, содержащим асинхронные данные.

подписка на событие

Другой распространенный вариант использования —componentWillMountподписаться на события вcomponentWillUnmountдля отмены подписки на соответствующее событие. Но на самом деле React не гарантирует, чтоcomponentWillMountПосле вызова тот же компонентcomponentWillUnmountтоже надо позвонить. Пример текущей версии, такой как рендеринг на стороне сервера,componentWillUnmountне будет вызываться на стороне сервера, поэтому вcomponentWillMountПодписка на события напрямую приведет к утечке памяти на стороне сервера. С другой стороны, после того, как React в дальнейшем включит режим асинхронного рендеринга, вcomponentWillMountПосле вызова рендеринг компонента также может быть прерван другими транзакциями, что приводит кcomponentWillUnmountне будет называться. а такжеcomponentDidMountНет такой проблемы, т.componentDidMountПосле звонка,componentWillUnmountЕго нужно вызвать позже, и существующая в компоненте подписка на событие очищается в соответствии с конкретным кодом.

План обновления

поставить существующийcomponentWillMountкод при переходе наcomponentDidMountВот и все.

componentWillReceiveProps

Обновлять состояние, определяемое реквизитами, и обрабатывать обратные вызовы в определенных случаях.

В старой версии React, если какое-то состояние самого компонента тесно связано с его пропсами, никогда не было очень элегантного способа обновить состояние, а его нужно обновлять вcomponentWillReceivePropsОпределите, одинаковы ли два реквизита до и после, и если они разные, обновите новые реквизиты до соответствующего состояния. Это уничтожило бы единственный источник данных о состоянии, сделав состояние компонента непредсказуемым и, с другой стороны, увеличив количество перерисовок компонента. Существует также много схожих бизнес-требований, таких как список, который можно перемещать по горизонтали.Выделенная в данный момент вкладка, очевидно, принадлежит состоянию самого списка, но во многих случаях бизнес-требования требуют перехода к списку извне, в соответствии с к входящему определенному значению A, непосредственно расположенному на вкладке.

В новой версии React официально предоставляет более краткую функцию жизненного цикла:

static getDerivedStateFromProps(nextProps, prevState)

Простой пример выглядит следующим образом:

// before
componentWillReceiveProps(nextProps) {	
  if (nextProps.translateX !== this.props.translateX) {
    this.setState({	
      translateX: nextProps.translateX,	
    });	
  }	
}

// after
static getDerivedStateFromProps(nextProps, prevState) {
  if (nextProps.translateX !== prevState.translateX) {
    return {
      translateX: nextProps.translateX,
    };
  }
  return null;
}

На первый взгляд кажется, что между ними нет существенной разницы, но я думаю, что это изменение может отражать глубокое понимание командой React разработки программного обеспечения, а именноКоманда React пытается ограничить или помочь разработчикам писать более удобный для сопровождения код JavaScript с помощью API на уровне фреймворка.. Чтобы объяснить это, давайте посмотрим на другой фрагмент кода:

// before
componentWillReceiveProps(nextProps) {
  if (nextProps.isLogin !== this.props.isLogin) {
    this.setState({	
      isLogin: nextProps.isLogin,	
    });
  }
  if (nextProps.isLogin) {
    this.handleClose();
  }
}
// after
static getDerivedStateFromProps(nextProps, prevState) {
  if (nextProps.isLogin !== prevState.isLogin) {
    return {
      isLogin: nextProps.isLogin,
    };
  }
  return null;
}

componentDidUpdate(prevProps, prevState) {
  if (!prevState.isLogin && this.props.isLogin) {
    this.handleClose();
  }
}

Вообще говоря, вcomponentWillReceiveProps, мы обычно делаем следующие две вещи: одна — обновлять состояние в соответствии с реквизитами, а другая — запускать некоторые обратные вызовы, такие как анимация или переходы по страницам. В старых версиях React нам нужны были обе эти вещи вcomponentWillReceivePropsв дела. В новой версии официальный переназначает состояние обновления и вызывает обратный вызов наgetDerivedStateFromPropsа такжеcomponentDidUpdate, что делает общую логику обновления компонента более понятной. И вgetDerivedStateFromPropsЭто также запрещает компонентам доступ к this.props, заставляя разработчиков сравнивать значения в nextProps и prevState, чтобы гарантировать, что когда разработчики используютgetDerivedStateFromPropsКогда эта функция жизненного цикла используется, она обновляет состояние компонента на основе текущих реквизитов, а не выполняет другие действия, которые делают состояние компонента более непредсказуемым.

План обновления

поставить существующийcomponentWillReceivePropsКод в состоянии обновления или обратного вызова соответственно вgetDerivedStateFromPropsа такжеcomponentDidUpdateВы можете переписать его соответствующим образом, обратите внимание на новые и старые функции жизненного цикла.prevProps,this.props,nextProps,prevState,this.stateс разница.

componentWillUpdate

Обработка побочных эффектов из-за смены реквизита

а такжеcomponentWillReceivePropsТочно так же многие разработчики такжеcomponentWillUpdateИнициировать некоторые обратные вызовы на основе изменений в свойствах. но не важноcomponentWillReceivePropsещеcomponentWillUpdate, может вызываться несколько раз за одно обновление, то есть написанная здесь функция обратного вызова также может вызываться несколько раз, что явно нежелательно. а такжеcomponentDidMountаналогичный,componentDidUpdateНет такой проблемы, в обновленииcomponentDidUpdateбудет вызываться только один раз, поэтому напишитеcomponentWillUpdateОбратные вызовы в миграции вcomponentDidUpdateможет решить эту проблему.

Чтение состояния элемента DOM перед обновлением компонента

другой общийcomponentWillUpdateВариант использования — прочитать текущее состояние элемента DOM перед обновлением компонента иcomponentDidUpdateобрабатываться соответствующим образом. Но после того, как React включает режим асинхронного рендеринга, фаза рендеринга и фаза фиксации не являются бесшовными, то есть состояние элементов DOM, прочитанных на фазе рендеринга, не всегда совпадает с фазой фиксации, что приводит кcomponentDidUpdateиспользуется вcomponentWillUpdateСостояние прочитанного элемента DOM небезопасно, потому что значение в это время, вероятно, будет недопустимым.

Чтобы решить эту проблему, упомянутую выше, React предоставляет новую функцию жизненного цикла:

getSnapshotBeforeUpdate(prevProps, prevState)

а такжеcomponentWillUpdateразные,getSnapshotBeforeUpdateбудет вызываться перед окончательным рендерингом, то есть вgetSnapshotBeforeUpdateСостояние элемента DOM, прочитанное вcomponentDidUpdateпоследовательный в. несмотря на то чтоgetSnapshotBeforeUpdateНе статический метод, но мы должны попробовать использовать его и для возврата значения. Затем это значение будет переданоcomponentDidUpdate, то мы можемcomponentDidUpdateобновить состояние компонента вgetSnapshotBeforeUpdateдля непосредственного обновления состояния компонента.

Пример, приведенный чиновником, выглядит следующим образом:

class ScrollingList extends React.Component {
  listRef = null;

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // Are we adding new items to the list?
    // Capture the scroll position so we can adjust scroll later.
    if (prevProps.list.length < this.props.list.length) {
      return (
        this.listRef.scrollHeight - this.listRef.scrollTop
      );
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // If we have a snapshot value, we've just added new items.
    // Adjust scroll so these new items don't push the old ones out of view.
    // (snapshot here is the value returned from getSnapshotBeforeUpdate)
    if (snapshot !== null) {
      this.listRef.scrollTop =
        this.listRef.scrollHeight - snapshot;
    }
  }

  render() {
    return (
      <div ref={this.setListRef}>
        {/* ...contents... */}
      </div>
    );
  }

  setListRef = ref => {
    this.listRef = ref;
  };
}

План обновления

поставить существующийcomponentWillUpdateФункция обратного вызова перенесена вcomponentDidUpdate. Если состояние элемента DOM требуется для запуска определенных функций обратного вызова, перенесите процесс сравнения или вычисления вgetSnapshotBeforeUpdate, затем вcomponentDidUpdateВ унифицированном состоянии обратного вызова или обновления триггера.

резюме

Наконец, давайте взглянем на сходства и различия до и после настройки функций жизненного цикла React с общей точки зрения:

Before

After

Три функции жизненного цикла, обведенные красным на первом рисунке, будут удалены в новой версии. Из двух приведенных выше изображений ясно видно, что все три удаляемые функции жизненного цикла вызываются перед рендерингом. В соответствии с первоначальным дизайном все три функции жизненного цикла могут выполнять действия с побочными эффектами, такими как отправка запросов, setState и т. д. В старых версиях React это может принести лишь некоторые потери производительности, но после того, как React включает режим асинхронного рендеринга, такие побочные эффекты уже не допустимы. Чтобы привести пример Git, после того, как разработчик зафиксировал 10 обновлений файлов, он делает еще одно обновление для текущего или других файлов, но при отправке он по-прежнему отправляет только 10 обновлений файлов, которые были только что зафиксированы. Это приведет к тому, что запись отправки будет несовместима с фактическим обновлением.Если вы хотите избежать этой проблемы, вам необходимо убедиться, что каждое обновление файла должно пройти стадию фиксации, а затем быть отправлено на удаленный конец, когда React переходит в режиме асинхронного рендеринга.

С другой стороны, чтобы проверить личное понимание и проверить стабильность новой версии, автор обновил несколько проектов, за которые я отвечаю, до React 16.3 и заменил все жизненные циклы, которые собираются удалить согласно обновлению. план упомянутый выше функция. В настоящее время все проекты работают нормально в продакшене и не получили плохих отзывов пользователей.

Конечно, вышеупомянутые изменения в этих функциях жизненного цикла не будут реализованы до React 17.0, что дает большинству разработчиков React достаточно времени, чтобы адаптироваться к этому изменению. Но если вы сопровождаете проект React с открытым исходным кодом (особенно библиотеку компонентов), найдите время, чтобы узнать больше об этом изменении функции жизненного цикла. Потому что это не только поможет вам лучше обновить свои проекты с открытым исходным кодом до последней версии React, но, что более важно, поможет вам заранее понять предстоящий режим асинхронного рендеринга.

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


Адрес личного блога:AlanWei/blog