Почему рекомендуется использовать componentDidUpdate
больше, чем функция обратного вызова setState
?
1. Последовательная логика
При использовании аргумента обратного вызова для setState()
у вас могут быть два отдельных вызова на setState()
в разных местах, которые оба обновляют одно и то же состояние, и вам не забудьте использовать один и тот же обратный вызов в обоих местах.
Типичным примером является вызов сторонней службы при каждом изменении состояния:
private method1(value) {
this.setState({ value }, () => {
SomeAPI.gotNewValue(this.state.value);
});
}
private method2(newval) {
this.setState({ value }); // forgot callback?
}
Вероятно, это логическая ошибка, поскольку, вероятно, вы захотите вызвать службу в любое время, когда значение изменится.
Вот почему componentDidUpdate()
рекомендуется:
public componentDidUpdate(prevProps, prevState) {
if (this.state.value !== prevState.value) {
SomeAPI.gotNewValue(this.state.value);
}
}
private method1(value) {
this.setState({ value });
}
private method2(newval) {
this.setState({ value });
}
Таким образом, служба гарантированно будет вызываться при каждом обновлении состояния.
Кроме того, состояние может быть обновлено из внешнего кода (например, Redux), и у вас не будет возможности добавить обратный вызов к этим внешним обновлениям.
2. Пакетные обновления
Аргумент обратного вызова setState()
выполняется после повторной визуализации компонента. Однако из-за пакетной обработки несколько вызовов на setState()
не гарантированно вызовут несколько обработчиков.
Рассмотрим этот компонент:
class Foo extends React.Component {
constructor(props) {
super(props);
this.state = { value: 0 };
}
componentDidUpdate(prevProps, prevState) {
console.log('componentDidUpdate: ' + this.state.value);
}
onClick = () => {
this.setState(
{ value: 7 },
() => console.log('onClick: ' + this.state.value));
this.setState(
{ value: 42 },
() => console.log('onClick: ' + this.state.value));
}
render() {
return <button onClick={this.onClick}>{this.state.value}</button>;
}
}
У нас есть два setState()
вызова в обработчике onClick()
, каждый из которых просто выводит новое значение состояния на консоль.
Можно ожидать, что onClick()
напечатает значение 7
, а затем 42
. Но на самом деле он печатает 42
дважды! Это связано с тем, что два вызова setState()
объединены и вызывают только один рендеринг.
Также у нас есть componentDidUpdate()
, который также печатает новое значение. Поскольку у нас только один рендеринг, он выполняется только один раз и печатает значение 42
.
Если вам нужна согласованность с пакетными обновлениями, обычно проще использовать componentDidMount()
.
2,1. Когда происходит дозирование?
Это не имеет значения.
Пакетирование - это оптимизация , и поэтому вы никогда не должны полагаться ни на пакетирование, ни на его возникновение. Будущие версии React могут выполнять более или менее пакетную обработку в различных сценариях.
Но, если вам необходимо знать, в текущей версии React (16.8.x) пакетирование происходит в асинхронных пользовательских обработчиках событий (например, onclick
) и иногда методах жизненного цикла, если React имеет полный контроль над исполнением. Во всех других контекстах никогда не используется пакетная обработка.
См. Этот ответ для получения дополнительной информации: https://stackoverflow.com/a/48610973/640397
3. Когда лучше использовать обратный вызов setState
?
Когда внешнему коду необходимо дождаться обновления состояния, вы должны использовать обратный вызов setState
вместо componentDidUpdate
и заключить его в обещание.
Например, предположим, что у нас есть Child
компонент, который выглядит следующим образом:
interface IProps {
onClick: () => Promise<void>;
}
class Child extends React.Component<IProps> {
private async click() {
await this.props.onClick();
console.log('Parent notified of click');
}
render() {
return <button onClick={this.click}>click me</button>;
}
}
И у нас есть Parent
компонент, который должен обновлять состояние при нажатии дочернего элемента:
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = { clicked: false };
}
private setClicked = (): Promise<void> => {
return new Promise((resolve) => this.setState({ clicked: true }, resolve));
}
render() {
return <Child onClick={this.setClicked} />;
}
}
В setClicked
мы должны создать Promise
, чтобы вернуться к потомку, и единственный способ сделать это - передать обратный вызов setState
.
Невозможно создать это Promise
в componentDidUpdate
, но даже если бы оно было, оно не будет работать должным образом из-за пакетирования.
Разное.
Поскольку setState
является асинхронным, я думал об использовании функции обратного вызова setState
(2-й аргумент), чтобы гарантировать выполнение кода после обновления состояния, аналогично .then()
для обещаний.
Обратный вызов для setState()
не вполне работает так же, как обещания, поэтому может быть лучше разделить ваши знания.
Особенно, если мне нужен повторный рендеринг между последующими setState
вызовами.
Зачем вам когда-либо нужно повторно визуализировать компонент между setState()
вызовами?
Единственная причина, которую я могу себе представить, состоит в том, что родительский компонент зависит от некоторой информации из дочернего элемента DOM, такой как его ширина или высота, и родительский элемент устанавливает некоторые реквизиты для дочернего элемента на основе этих значений.
В вашем примере вы вызываете this.props.functionFromParentComponent()
, который возвращает значение, которое вы затем используете для вычисления некоторого состояния.
Во-первых, следует избегать производного состояния, так как запоминание является гораздо лучшим вариантом. Но даже в этом случае, почему бы просто не сделать так, чтобы родитель передал значение непосредственно как опору? Тогда вы можете по крайней мере вычислить значение состояния в getDerivedStateFromProps()
.
//firstVariable may or may not have been changed,
//now someVariableBeforeSetStateCall may or may not get updated at the same time
//as firstVariableWasSet or firstVariable due to async nature of setState
Эти комментарии не имеют большого смысла для меня. Асинхронная природа setState()
не подразумевает ничего о том, что состояние не обновляется должным образом. Код должен работать как задумано.