[Перевод] Понимание жизненного цикла React-компонента

React предоставляет разработчикам множество методов и «хуков», которые вызываются во время жизненного цикла компонента, они позволяют нам обновлять UI и состояние приложения. Когда необходимо использовать каждый из них, что необходимо делать и в каких методах, а от чего лучше отказаться, является ключевым моментом к пониманию как работать с React.
Update:
В React 16.3 появились два дополнительных метода жизненного цикла и несколько методов объявлено устаревшими, подробности по ссылке medium.com/@baphemot/understanding-react-react-16–3-component-life-cycle-23129bc7a705
(Прим. переводчика: Хотя некоторые методы и объявлены устаревшими, их описание, на мой взгляд, все равно будет полезно, хотя бы тем разработчикам, которые работают с предыдущими версиями 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 
  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 дереве:
kq8anm2cgymtxl1cad5hriolu18.png

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

oc8whw24l7vjuze_s01n74uzgu4.png

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

7isiwhyz5diakdk_fbbqr0cqnxk.png

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

8dukwkh7kvowk1r_ealjsgsl9z0.png

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

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

@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 суммировал все изменения в одной картинке:
6t7slszbqm-tvq5f8twwanrmdgw.jpeg

Спасибо за внимание!

© Habrahabr.ru