React. Lifecycle. Key. Ref
Жизненный цикл компонента
В процессе работы компонент проходит через ряд этапов жизненного цикла. На каждом из этапов вызывается определенная функция, в которой мы можем определить какие-либо действия:
Основные методы жизненного цикла:
constructor(props)
Конструктор, в котором происходит начальная инициализация компонента.Если вы не инициализируете состояние и не привязываете методы, вам не нужно реализовывать конструктор для вашего компонента React. Конструктор для компонента React вызывается до его монтирования. При реализации конструктора для подкласса React.Component вы должны вызывать super(props) перед любым другим оператором. В противном случае this.props будет неопределенным в конструкторе, что может привести к ошибкам. Как правило, в React конструкторы используются только для двух целей:
- Инициализация локального состояния путем присвоения объекта this.state.
- Привязка методов обработчика событий к экземпляру.
Вы не должны вызывать setState() в конструкторе!! Вместо этого, если ваш компонент должен использовать локальное состояние, присвойте начальное состояние this.state непосредственно в конструкторе:
constructor(props) {
super(props);
// Не вызывать setState!!! конструктор только для задания начального состояния
this.state = { counter: 0 };
this.handleClick = this.handleClick.bind(this);
}Конструктор - это единственное место, где вы должны назначить this.state напрямую. Во всех других методах вам нужно использовать this.setState().
Существует альтернативный синтаксис, предлагаемый для упрощения называемый class fields. Это еще не входит в спецификацию ECMAScript, но если вы используете транспилер Babel, этот синтаксис должен работать из коробки.
Пример:
/*
// Not required anymore
constructor() {
super();
this.state = {
count: 1
}
}
*/
state = { count: 1 };Избегайте копирования props в state! Это распространенная ошибка (особенно новичков):
constructor(props) {
super(props);
// Don't do this!!!!
this.state = { color: props.color };
}Проблема заключается в том, что он не нужен (вместо этого можно использовать this.props.color напрямую), а также создает ошибки (обновления props.color не будут отражаться в state).
Используйте этот шаблон, только если вы намеренно хотите игнорировать обновления props. В этом случае имеет смысл переименовать реквизит, чтобы он назывался initialColor или defaultColor. Затем вы можете принудительно заставить компонент «сбрасывать» свое внутреннее состояние, меняя ключ при необходимости.
getDerivedStateFromProps()
static getDerivedStateFromProps(nextProps, prevState) вызывается непосредственно перед вызовом метода рендеринга как при первоначальном монтировании, так и при последующих обновлениях. В качестве параметров получает nextProps - уже измененные данные, но компонент еще не был изменен с этими данными и prevState, на основании этих входящих данных можно сделать сравнение и вернуть объект прямо в state компонента без вызова функции setState().
state = { cachedSomeProp: null };
static getDerivedStateFromProps(nextProps, prevState) {
// do things with nextProps.someProp and prevState.cachedSomeProp
return {
cachedSomeProp: nextProps.someProp,
..
};
}Этот метод существует для редких случаев использования, когда состояние зависит от изменений props с течением времени.
Например, это может быть удобно для реализации компонента
- getDerivedStateFromProps должен возвращать объект, чтобы обновить состояние, или null, чтобы ничего не обновлять.
- В getDerivedStateFromProps методе нет доступа к this
- Метод запускается при каждом рендере, независимо от причины.
render()
Метод render() является единственным обязательным методом в компоненте класса. При вызове он должен проверить this.props и this.state и вернуть один из следующих типов:
- React element. Обычно создается через JSX. Например, <div /> и <MyComponent /> являются элементами React, которые инструктируют React визуализировать узел DOM или другой пользовательский компонент, соответственно.
- Массивы и фрагменты. Позволит вам вернуть несколько элементов из рендера.
- Порталы. Пусть вы выводите детей в другое поддерево DOM.
- Booleans или null. Ничего не рендерит (В основном существует для поддержки шаблона return test && <Child />, где test является boolean.)
Функция render() должна быть чистой, то есть она не изменяет состояние компонента, возвращает один и тот же результат при каждом вызове и не взаимодействует напрямую с браузером.
Если вам нужно взаимодействовать с браузером, вместо этого выполняйте свою работу в componentDidMount() или других методах жизненного цикла. Сохранение render() в чистоте - это облегчит вам и не только понимание компонентов.
render() не будет вызываться, если shouldComponentUpdate() возвращает false.
componentDidMount()
Вызывается после рендеринга компонента. Здесь можно выполнять запросы к удаленным ресурсам. В данный момент у нас есть возможность использовать refs, а следовательно это то самое место, где мы хотели бы указать установку фокуса. Так же, таймауты, ajax-запросы и взаимодействие с другими библиотеками стоит обрабатывать здесь. Этот метод жизненного цикла будет вызван лишь раз во всем цикле данного компонента и будет сигнализировать, что компонент и все его дочерние компоненты прорисовались без ошибок. РЕКОМЕНДУЕТСЯ не делать:
- Не вызывайте this.setState т.к. это вызывает перерисовку! Исключение из правила выше это апдейт состояния, которое базируется на каких-либо DOM свойствах, которые могут вычислены только после того, как компонент перерисовывался (например позиция/размеры каких-либо DOM узлов). Но будьте внимательны и предотвращайте повторное обновление если значение фактически не изменилось, т.к. это может привести к циклической перерисовке.
componentDidUpdate()
Вызывается сразу после обновления компонента (если shouldComponentUpdate возвращает true). В качестве параметров передаются старые значения объектов props,state, context. Эта функция будет вызываться после того как отработала функция render, в каждом цикле перерисовки. Это означает, что вы можете быть уверены, что компонент и все его дочерние компоненты уже перерисовали себя. В связи с этим этот метод жизненного цикла является единственной функцией, что гарантированно будет вызвана только раз в каждом цикле перерисовки, поэтому любые сайд-эффекты рекомендуется выполнять именно здесь. Как componentWillUpdate и componentWillRecieveProps в эту функцию передается предыдущие props, состояние state и контекст context, даже если в этих значениях не было изменений. Поэтому разработчики должны вручную проверять переданные значения на изменения и только потом производить различные апдейт операции.
componentDidUpdate(prevProps, prevState, prevContext) {
if(prevProps.myProps !== this.props.myProp) {
// У this.props.myProp изменилось значение
// Поэтому мы можем выполнять любые операции для которых
// нужны новые значения и/или выполнять сайд-эффекты
// вроде AJAX вызовов с новым значением - this.props.myProp
// this.setState({...})
}
}Вы можете вызвать setState() немедленно в componentDidUpdate(), но учтите, что он должен быть заключен в условие, как в примере выше, иначе вы вызовете бесконечный цикл. Это также вызвало бы дополнительный повторный рендеринг, который, хотя и невидим для пользователя, может повлиять на производительность компонента. Если вы пытаетесь «отразить» какое-то состояние для props, идущих сверху, рассмотрите возможность использования props напрямую.
Если ваш компонент реализует жизненный цикл getSnapshotBeforeUpdate() (что встречается редко), возвращаемое им значение будет передано в качестве дополнительного параметра «снимка» в componentDidUpdate(). В противном случае этот параметр будет неопределенным.
componentWillUnmount()
Вызывается перед удалением компонента из DOM componentWillUnmount() вызывается непосредственно перед размонтированием и уничтожением компонента. Выполните любую необходимую очистку в этом методе, такую как аннулирование таймеров, отмена сетевых запросов или очистка любых подписок, созданных в componentDidMount(). Вы не должны вызывать setState() в componentWillUnmount(), потому что компонент никогда не будет перерисован. Когда экземпляр компонента размонтирован, он никогда не будет снова подключен.
Остановить метод жизненного цикла componentWillUnmount невозможно!
Дополнительные методы жизненного цикла
shouldComponentUpdate()
Этот метод жизненного цикла отвечает на вопрос - должен ли компонент обновиться.
По умолчанию, все компоненты будут перерисовывать себя всякий раз, когда их состояние (state) изменяется, изменяется context или они принимают props от родителя. Если перерисовка компонента довольно тяжелая (например генерация чарта, графика) или не рекомендуется по каким-либо перфоманс причинам, то у разработчиков есть доступ к специальной функции, которая будет вызываться всякий раз при апдейт цикле. shouldComponentUpdate будет вызываться с следующими параметрами (nextProps, nextState, nextContext) И разработчик может использовать эти параметры для того чтобы решить нужно ли делать перерисовку компонента или вернуть false и предотвратить ее. В противном случае от вас ожидают, что вы вернете true.
shouldComponentUpdate(nextProps, nextState, nextContext) {
if(nextProps.id !== this.props.id) {
return false;
}
return true;
}shouldComponentUpdate всегда должен возвращать true или false!
Этот метод существует только для оптимизации производительности. Не полагайтесь на него, чтобы «предотвратить» рендеринг, так как это может привести к ошибкам. В настоящее время, если shouldComponentUpdate() возвращает false, то методы такие как - UNSAFE_componentWillUpdate(), render() и componentDidUpdate() вызываться не будут. РЕКОМЕНДУЕТСЯ не делать:
- Не выполняйте никаких сайд-эффектов (Вызовы AJAX и т.д.)
- Не вызывайте this.setState
getSnapshotBeforeUpdate()
Новый метод жизненного цикла добавленный в React 16.3 getSnapshotBeforeUpdate() вызывается непосредственно перед тем, как последний обработанный вывод будет зафиксирован, например, в ДОМ. Это позволяет вашему компоненту захватывать некоторую информацию из DOM (например, положение прокрутки) до того, как она потенциально может быть изменена. Любое значение, возвращаемое этим жизненным циклом, будет передано в качестве параметра componentDidUpdate(). Этот вариант использования не распространен, но он может возникать в пользовательских интерфейсах, таких как цепочка чата, которым необходимо обрабатывать положение прокрутки особым образом.
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// Добавляем ли мы новые элементы в список?
// Захват позиции прокрутки, чтобы мы могли настроить прокрутку позже.
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// Если у вас есть значение моментального снимка, мы только что добавили новые элементы.
// Отрегулируйте прокрутку, чтобы эти новые элементы не выталкивали старые из поля зрения.
// (snapshot here is the value returned from getSnapshotBeforeUpdate)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.listRef}>{/* ...contents... */}</div>
);
}
}В вышеприведенном примере важно прочитать свойство scrollHeight в getSnapshotBeforeUpdate, потому что могут быть задержки между жизненными циклами фазы «рендеринга» и фазами «фиксации» (такими как getSnapshotBeforeUpdate и componentDidUpdate).
- getSnapshotBeforeUpdate() должен возвращать объект, чтобы обновить состояние, или null, чтобы ничего не обновлять.
- getSnapshotBeforeUpdate() используется в паре с componentDidUpdate() без него React выдаст ошибку
- в качеcтве параметров получает (prevProps, prevState, prevContext)
Списки и ключи. Keys
Ключи помогают React определить, какие элементы были изменены, добавлены или удалены. Элементы внутри массива должны быть обеспечены ключами, чтобы иметь стабильную идентичность:
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);Лучший способ подобрать ключ — использовать строку, которая однозначно идентифицирует элемент списка среди остальных. Чаще всего, в качестве ключей вы будете использовать идентификаторы (ID) из ваших данных:
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);Если у вас нет стабильных идентификаторов (ID) для сгенерированных элементов, в крайнем случае, в качестве ключа вы можете использовать индекс index элемента:
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
<li key={index}>
{todo.text}
</li>
);Не рекомендуется использовать индексы для ключей в случае если элементы могут изменить порядок, так как это будет происходить медленно.
Для React ключи служат в качестве подсказки, но они не передаются вашим компонентам. Если вам нужно одно и то же значение в компоненте, предоставьте его непосредственно в качестве props с другим именем:
const content = posts.map((post) =>
<Post
key={post.id}
id={post.id}
title={post.title}
/>
);ref
refs используются для получения ссылки на узел DOM (Document Object Model) или компонента в React. Если кратко, то Refs возвращает ссылку на элемент. Почти как в старые добрые getElementById. Почему бы не брать ссылку по ID элемента <input id=“input” /> ? Выглядит неплохо, у нас есть id элементов и можно получать ссылки с помощью getElementById. Но тут есть ряд минусов:
- Под каждый элемент должна быть уникальная id, тогда как названия ref могут повторяться в разных компонентах.
- Это противоречит “философии” Реакта (самое важное, конечно)
Когда использовать ref ?
Разработчики React настоятельно рекомендуют использовать ref только в случаях:
- Для управления фокусом, выделением текста или воспроизведением мультимедиа.
- Для запуска императивных анимаций, или вообще анимации.
- Для интеграции со сторонними библиотеками DOM.
Во всех остальных случаях не рекомендуется использовать ref так как остальные случаи не являются зоной ответственности ref, также избегайте использования ссылок для всего, что может быть сделано декларативно.
Доступ к ref
После первого render они становятся доступны во всех методах. То есть объявленный ref уже будет доступен в componentDidMount и во всех повторяющихся методах жизненного цикла.
Создание ref
В React есть несколько основных способов как можно создать ref:
Через строковое имя
<div ref="itsMyDiv" />Через такое определение DOM элемент будет доступен в компоненте в специальном объекте refs который существует наряду со state и props. В этот объект будут помещены все refs которые объявлены через строковое имя в пределах этого компонента.
this.refs.itsMyDiv // тут будет доступен ваш элементТакой подход считается устаревшим и в будущих версиях React будет убран.
Через callback функцию
Атрибут ref принимает функцию обратного вызова, и вызывает ее после того, как компонент монтируется в DOM или удаляется из него. Когда атрибут ref используется в элементе HTML, функция обратного вызова принимает базовый элемент DOM в качестве аргумента. Например, следующий код использует функцию обратного вызова, указанную в ref, для сохранения ссылки на узел DOM:
class CustomTextInput extends React.Component {
focus = () => {
// Установка фокуса на поле текстового ввода (input) с явным использованием исходного API DOM
this.textInput.focus();
}
render() {
// Использование обратного вызова `ref` для сохранения ссылки на поле текстового ввода (input)
// как элемента DOM в this.textInput.
return (
<div>
<input
type="text"
ref={(input) => { this.textInput = input; }}
/>
<input
type="button"
value="Focus the text input"
onClick={this.focus}
/>
</div>
);
}
}React вызывает функцию обратного вызова ref с элементом DOM в качестве аргумента когда компонент монтируется, и со значением null в качестве аргумента когда компонент удаляется. Через функцию обратного вызова DOM элемент будет доступен в компоненте напрямую через this и имя ref.
this.textInput // в предыдущем примере мы объявили ref в переменную textInputНельзя использовать атрибут ref с компонентом, построенным на функции, т.к. функция не имеет экземпляров класса. Однако, вы можете использовать атрибут ref внутри такого компонента:
function CustomTextInput(props) {
// textInput задекларирован здесь, т.к. обратный вызов ref ссылается на него
let textInput = null;
function handleClick() {
textInput.focus();
}
return (
<div>
<input
type="text"
ref={(input) => { textInput = input; }}
/>
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}React.CreateRef()
React предоставлял два способа управления refs: указание ref обычной строкой и callback-вызов. Хотя и указание ref просто в качестве строки было удобнее, это имело несколько минусов и поэтому мы рекомендовали использовать вариант с callback’ом. Версия React 16.3 добавляет новую опцию для управления refs, которая предлагает удобство указания ref в виде строки без недочетов:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
componentDidMount() {
this.inputRef.current.focus();
}
render() {
return <input type="text" ref={this.inputRef} />;
}
}При таком подходе DOM элемент будет доступен в компоненте напрямую через this и имя ref которое вы задаете в конструкторе.
Callback refs будут поддерживаться и далее в новом createRef API. Не спешите заменять callback refs в ваших компонентах. Они более гибкие, поэтому мы оставим их для будущего продвинутого использования.