Понимание жизненного цикла React-компонента

https://medium.com/@baphemot/understanding-reactjs-component-life-cycle-823a640b3e8d
  • Перевод
  • Tutorial
React предоставляет разработчикам множество методов и «хуков», которые вызываются во время жизненного цикла компонента, они позволяют нам обновлять UI и состояние приложения. Когда необходимо использовать каждый из них, что необходимо делать и в каких методах, а от чего лучше отказаться, является ключевым моментом к пониманию как работать с React.

Update:

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

Constructor:

Конструкторы являются основной ООП – это такая специальная функция, которая будет вызываться всякий раз, когда создается новый объект. Очень важно вызывать функцию super в случаях, когда наш класс расширяет поведение другого класса, который имеет конструктор. Выполнение этой специальной функции будет вызывать конструктор нашего родительского класса и разрешать ему проинициализировать себя. Вот почему мы имеем доступ к this.props только после вызова super. (имеется ввиду вызов super(props) в классе-наследнике React.Component)

Поэтому конструкторы — это отличное место для инициализации компонента – создание любых полей (переменные начинающиеся с this.) или инициализации состояния компонента на основе полученных props.

Это также единственное место где вы можете изменять/устанавливать состояние (state) напрямую перезаписывая поле this.state. Во всех других случаях необходимо использовать this.setState.

ДЕЛАЙТЕ:

  • Устанавливайте изначальное состояние компонента
  • Если не используется class properties синтаксис – подготовьте все поля класса и вызовете bind на тех функциях, что будут переданы как коллбеки.

НЕ ДЕЛАЙТЕ:

  • Не выполняйте никаких сайд-эффектов (side effects) (Вызовы AJAX и т.д.)

[deprecated]componentWillMount

componentWillMount не очень отличается от конструктора – она также вызывается лишь раз в изначальном жизненном цикле. Вообще исторически были некоторые причины использовать componentWillMount поверх конструктора — смотри react-redux issue, но на данный момент практика, описанная там, устарела.

У многих возможно появится соблазн использовать эту функцию для отправки реквеста на получение данных и они будут ожидать, что данные будут доступны прежде чем отработает изначальный render. Но это не тот случай – хотя реквест и будет инициализирован перед render, он не успеет выполниться прежде чем render будет вызван.

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

Также важно отметить, что эта функция вызывается, когда используется рендеринг на стороне сервера (server side rendering), когда ее антипод – componentDidMount не будет вызван на сервере, но будет на клиенте. Поэтому если некоторый сайд-эффект нацелен на серверную часть, эта функция может быть использована как исключение.

И наконец функция setState может быть свободно использована и не будет вызывать перерисовку компонента.

ДЕЛАЙТЕ:

  • Обновляйте состояние через this.setState
  • Выполняйте последние оптимизации
  • Вызывайте сайд-эффекты (Вызов AJAX и т.д.) только в случае server-side-rendering.

НЕ ДЕЛАЙТЕ:

  • Не выполняйте никаких сайд-эффектов (Вызов AJAX и т.д.) на стороне клиента.

[deprecated]componentWillReceiveProps(nextProps)

Эта функция будет вызываться при каждом апдейт жизненном цикле, который будет происходить при изменениях в props (когда перерисовывается родительский компонент) и будет принимать маппинг всех передаваемых props, не важно изменялось значение какого-либо свойства или нет с предыдущей фазы перерисовки.

Эта функция будет идеальна, если у вас есть какой-нибудь компонент, часть состояния которого (state), зависит от props передаваемых от родительского компонента, т.к. вызов this.setState здесь не будет приводить к дополнительной перерисовке.

Запомните, что т.к. эта функция вызывается со всеми props, даже с теми что не менялись, от разработчика ждут, что он напишет проверку, чтобы понять поменялось ли актуальное значение какого-либо свойства или нет.

Для примера:

componentWillReceiveProps(nextProps) {
  if(nextProps.myProp !== this.props.myProps) {
    // nextProps.myProp имеет другое значение, чем наше текущее myProps
    // поэтому мы можем что-нибудь рассчитать базируясь на новом значении.
  }
}

В связи с тем, что в React Fiber (после 16 beta) эта функция может вызываться несколько раз перед функцией render, не рекомендуется выполнять здесь никакие операции вызывающие сайд-эффекты.

ДЕЛАЙТЕ:

  • Синхронизируйте состояние (state) с props

НЕ ДЕЛАЙТЕ:

  • Не выполняйте никаких сайд-эффектов (Вызовы AJAX и т.д.)

shouldComponentUpdate(nextProps, nextState, nextContext)

По умолчанию, все компоненты будут перерисовывать себя всякий раз, когда их состояние (state) изменяется, изменяется контекст или они принимают props от родителя. Если перерисовка компонента довольно тяжелая (например генерация чарта, графика) или не рекомендуется по каким-либо перфоманс причинам, то у разработчиков есть доступ к специальной функции, которая будет вызываться всякий раз при апдейт цикле.

Эта функция будет вызываться с следующими значениями props, состоянием (state) и объектом. И разработчик может использовать эти параметры для того чтобы решить нужно ли делать перерисовку компонента или вернуть false и предотвратить ее. В противном случае от вас ожидают, что вы вернете true.

ДЕЛАЙТЕ:

  • Используйте для оптимизации производительности компонента

НЕ ДЕЛАЙТЕ:

  • Не выполняйте никаких сайд-эффектов (Вызовы AJAX и т.д.)
  • Не вызывайте this.setState

[deprecated]componentWillUpdate(nextProps, nextState)

Если мы не реализовали функцию shouldComponentUpdate или же решили, что компонент должен обновиться в этом рендер цикле, вызовется другая функция жизненного цикла. Эта функция в основном используется для того чтобы сделать синхронизацию между состоянием (state) и props в случае если часть состояния компонента базируется на каких-либо props.

В случаях когда shouldComponentUpdate реализована, функция componentWillUpdate может быть использована вместо componentWillReceiveProps, т.к. она будет вызываться только тогда, когда компонент действительно будет перерисован.

Подобно всем другим componentWill* функциям, эта функция может быть вызывана несколько раз перед render, поэтому не рекомендуется выполнять здесь никакие операции вызывающие сайд-эффекты.

ДЕЛАЙТЕ:

  • Синхронизируйте состояние (state) с props

НЕ ДЕЛАЙТЕ:

  • Не выполняйте никаких сайд-эффектов (Вызовы AJAX и т.д.)

componentDidUpdate(prevProps, prevState, prevContext)

Эта функция будет вызываться после того как отработала функция render, в каждом цикле перерисовки. Это означает, что вы можете быть уверены, что компонент и все его дочерние компоненты уже перерисовали себя.

В связи с этим эта функция является единственной функцией, что гарантировано будет вызвана только раз в каждом цикле перерисовки, поэтому любые сайд-эффекты рекомендуется выполнять именно здесь. Как componentWillUpdate и componentWillRecieveProps в эту функцию передается предыдущие props, состояние (state) и контекст, даже если в этих значениях не было изменений. Поэтому разработчики должны вручную проверять переданные значения на изменения и только потом производить различные апдейт операции.

componentDidUpdate(prevProps) {
  if(prevProps.myProps !== this.props.myProp) {
    // У this.props.myProp изменилось значение
    // Поэтому мы можем выполнять любые операции для которых
    // нужны новые значения и/или выполнять сайд-эффекты
    // вроде AJAX вызовов с новым значением - this.props.myProp
  }
}

ДЕЛАЙТЕ:

  • Выполняйте сайд-эффекты (Вызовы AJAX и т.д.)

НЕ ДЕЛАЙТЕ:

  • Не вызывайте this.setState т.к. это будет вызывать циклическую перерисовку.

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

componentDidCatch(errorString, errorInfo)

Дополнение в React 16 – этот метод жизненного цикла является особым, т.к. он позволяет реагировать на события, происходящие в дочернем компоненте, а конкретно на любые неперехваченные ошибки в любом из дочерних компонентов.

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

componentDidCatch(errorString, errorInfo) {
  this.setState({
    error: errorString
  });
  ErrorLoggingTool.log(errorInfo);
}
render() {
  if(this.state.error) return <ShowErrorMessage error={this.state.error} />
  return (
    // render normal component output
  );
}


Когда происходит какая-либо ошибка, эта функция вызывается с следующими параметрами:

  • errorString — .toString() сообщение о ошибке
  • errorInfo – объект с одним полем componentStack, которое содержит стектрейс, где произошла ошибка.

in Thrower
    in div (created by App)
    in App

componentDidMount

Эта функция будет вызвана лишь раз во всем жизненном цикле данного компонента и будет сигнализировать, что компонент и все его дочерние компоненты отрисовались без ошибок.

Т.к. эта функция гарантирована будет вызвана лишь раз, то это превосходный кандидат для выполнения любых сайд-эффектов, как то AJAX запросы.

ДЕЛАЙТЕ:

  • Выполняйте сайд-эффекты (Вызовы AJAX и т.д.)

НЕ ДЕЛАЙТЕ:

  • Не вызывайте this.setState т.к. это вызовет перерисовку.

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

componentWillUnmount

Используйте эту функцию для «очистки» после компонента, если он использует таймеры (setTimeout, setInterval), открывает сокеты или производит любые операции, которые нуждаются в закрытии или удалении.

ДЕЛАЙТЕ:

  • Удаляйте таймеры и слушателей (listeners) созданных во время жизни компонента.

НЕ ДЕЛАЙТЕ:

  • Не вызывайте this.setState, не стартуйте новых слушателей или таймеры.

Циклы компонента


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

Создание компонента

Первый цикл это создание компонента, которое обычно происходит при первом обнаружении компонента в распарсенном JSX дереве:


Компонент перерисовывается в связи с перерисовкой родительского компонента



Компонент перерисовывается в связи с внутренними изменениями (например вызов this.setState())



Компонент перерисовывается в связи с вызовом this.forceUpdate



Компонент перерисовывается в связи с перехватом ошибки

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



@James_k_nelson — недавно опубликовал componentWillRecieveProps симулятор. ТУТ вы можете найти и поиграться с этим симулятором.

React 16.3+ жизненный цикл компонента


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

static getDerivedStateFromProps(nextProps, prevState)

Основная ответственность этой новой функции — это убедиться, что состояние (state) и props синхронизированы, когда это необходимо. Ее основной смысл — это замена componentWillRecieveProps.

getDerivedStateFromProps – это статическая функция и поэтому не имеет доступа к this – вместо этого от вас ожидают, что вы вернете объект, который будет смержен в будущее состояние компонента (в точности как работа с setState!)

Эта функция используется, когда компонент обновляется, но также и когда он монтируется, сразу после вызова конструктора, поэтому вам больше не нужно использовать конструктор если вы хотите установить начальное состояние компонента из props.

getSnapshotBeforeUpdate(prevProps, prevState)

Другая из двух новых функций, вызывается в так называемой “pre-commit фазе”, прямо перед изменениями из VDOM, которые должны быть отображены в DOM.

Ее можно использовать в основном, если вам нужно прочитать текущее состояние DOM.
Например у вас есть приложение, в котором новые сообщения добавляются сверху экрана – если пользователь будет скроллить вниз, и добавится новое сообщение, экран будет «прыгать» и это сделает UI тяжелее в использовании. Добавлением getSnapshotBeforeUpdate вы сможете рассчитать текущее положение скролла и восстанавливать его через апдейт DOM-а.

Хотя функция не является статической, рекомендуется возвращать значение, а не апдейтить компонент. Возвращаемое значение будет передано в componentDidUpdate как 3-й параметр.

Устаревшие функции

Несмотря на то, что новые функции делают ваш переход на AsyncMode легче, вас не заставляют мигрировать весь ваш код. Следующие функции будут маркированы как устаревшие и в следующих релизах переименованы:

  • componentWillRecieveProps – UNSAFE_componentWillRecieveProps
  • componentWillUpdate – UNSAFE_componentWillUpdate

Вы увидите варнинги в следующей major версии, и функции будут переименованы (переименованные версии будут сохранены!) в версии 17.0

Dan Abramov суммировал все изменения в одной картинке:



Спасибо за внимание!
Поделиться публикацией

Комментарии 14

  • НЛО прилетело и опубликовало эту надпись здесь
      0
      Официальная рекомендация из доков по реакту это избегать сайд-эффектов в конструкторе. reactjs.org/docs/react-component.html#constructor
      Ну и комментарий Дэна Абрамова на эту тему. Вообще довольно туманное объяснение.
      (https://github.com/reactjs/reactjs.org/issues/302)

      If it's an async request, it won't be fulfilled by the time the component mounts anyway, regardless of where you fire it. This is because JS is single threaded, and the network request can't «come back» and be handled while we are still rendering. So the difference between firing it earlier and later is often negligible.

      You're right that it matters in some rare cases though and for those cases it might make sense to break the recommendation. But you should be extra cautious as state can update before mounting, and if your data depends on state, you might have to refetch in that case. In other words: when in doubt, do it in componentDidMount.

      The specific recommendation to avoid side effects in the constructor and Will* lifecycles is related to the changes we are making to allow rendering to be asynchronous and interruptible (in part to support use cases like this better). We are still figuring out the exact semantics of how it should work, so at the moment our recommendations are more conservative. As we use async rendering more in production we will provide a more specific guidance as to where to fire the requests without sacrificing either efficiency or correctness. But for now providing a clear migration path to async rendering (and thus being more conservative in our recommendations) is more important.
        +1
        Если делать те же ajax-запросы в конструкторе или же в componentWillMount, то мы не исключаем появление каких-либо ошибок, вследтвие чего компонент не будет отрисован. И в данном случае будут отправлены лишние запросы. Поэтому и рекомендуется все сайд-действия производить в componentDidMount
        • НЛО прилетело и опубликовало эту надпись здесь
          0

          А чего вы пытаетесь достичь путем их вызова в конструкторе?


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


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

          • НЛО прилетело и опубликовало эту надпись здесь
              0
              Вы неверно перевели его комментарий, он нигде не писал что все равно не будет ускорения.

              А как еще вот это понимать?


              So the difference between firing it earlier and later is often negligible.

              разница если и есть, то пренебрежимо мала, экономия на спичках получается.


              в каких случаях React захочет создать инстанс но не станет монтировать

              Это случится в будущих релизах:


              The specific recommendation to avoid side effects in the constructor and Will* lifecycles is related to the changes we are making to allow rendering to be asynchronous and interruptible
                +1
                Про монтирование — в тестах например. Просто если напишите в коде вызов компонентов до их рендеринга. В общем везде, где используете компонент, ещё не начав рендерить его в дом или вообще там, где дом нет.
            +1
            componentDidMount…
            НЕ ДЕЛАЙТЕ:
            • Не вызывайте this.setState т.к. это будет вызывать циклическую перерисовку.

            Это надо понимать ошибка?
              0
              Да, спасибо. Убрал слово циклическая.
                0
                После асинхронного вызова я кладу данные в state через this.setState, и вот тут мне кажется можно запутаться, т.к вроде нельзя делать this.setState в componentDidMount
                Может лучше уточнить…
                Не вызывайте this.setState синхронно или в синхронных функциях т.к. это вызовет перерисовку
                  0

                  Промахнулся ответом

              0

              Ну this.setState в любом случае перерисовку вызовет. Но в целом да, имеется ввиду вызов setState в самом методе, а не в коллбеке. В исходной статье есть такой же комментарий.

                +1
                Есть такая хорошая схема по тому, где можно юзать setState, а где нельзя.
                Единственно, что с выходом 16.3 устарела немного.

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое