Асинхронная природа SetState ()
Об асинхронном характере setState ()
Суть:
React собирает все обновления и рендерит за один приход( первичная оптимизация). Однако, в некоторых случаях React не имеет контроля над приложением, поэтому обновления производятся синхронно, например. eventListeners, Ajax, setTimeout и аналогичные веб-API.
Основная идея:
setState () не сразу мутирует this.state, но создает состояния в ожидании перехода. Доступ к this.state после вызова этого метода может потенциально вернуть текущее значение. Нет никакой гарантии синхронной работы вызовов setState, и вызовы могут быть сгрупированы для повышения производительности.
Выполните приведенный ниже код, и вы получите следующее замечания:
Вы можете увидеть, что в каждой ситуации (addEventListener, setTimeout или AJAX-вызов) состояние до и после могут быть разными. И этот рендер вызывается сразу же после запуска метода setState. Но почему так происходит? Дело в том, что React не понимает и, следовательно, не может управлять кодом, который не живет внутри библиотеки. Timeouts или вызовы AJAX, например, написаны вне контекста React.
Итак, почему же React синхронно обновляет состояние в этих случаях. Ну, потому что он пытается быть как можно более защищенным. Неконтролируемое означает, что он не в состоянии выполнять какие-либо первоочередные оптимизации, поэтому лучше обновлять состояние на месте и следить за тем, чтобы следующий код имел доступ к последней доступной информации.
class TestComponent extends React.Component {
constructor(...args) {
super(...args);
this.state = {
dollars: 10
};
this._saveButtonRef = (btn => { this._btnRef = btn });
[
'_onTimeoutHandler',
'_onMouseLeaveHandler',
'_onClickHandler',
'_onAjaxCallback',
].forEach(propToBind => {
this[propToBind] = this[propToBind].bind(this);
});
}
componentDidMount() {
// Добавить пользовательское событие `addEventListener`
//
// Список поддерживаемых событий React включает `mouseleave`
// называеться `onMouseLeave`
//
// Однако мы не добавляем событие, так как это бует мутировать наше состояние
//
// Проверьте список здесь - https://facebook.github.io/react/docs/events.html
this._btnRef.addEventListener('mouseleave', this._onMouseLeaveHandler);
// Добавим SetTimeout
//
// Опят таки, это будет влияет на мутировать наше состояние
// mutates
setTimeout(this._onTimeoutHandler, 10000);
// Сделаем AJAX запрос
fetch('https://api.github.com/users')
.then(this._onAjaxCallback);
}
render() {
console.log('State in render: ' + JSON.stringify(this.state));
return (
<button
ref={this._saveButtonRef}
onClick={this._onClickHandler}>
'Click me'
</button>
);
}
_onClickHandler() {
console.log('State before (_onClickHandler): ' + JSON.stringify(this.state));
this.setState({
dollars: this.state.dollars + 10
});
console.log('State after (_onClickHandler): ' + JSON.stringify(this.state));
}
_onMouseLeaveHandler() {
console.log('State before (mouseleave): ' + JSON.stringify(this.state));
this.setState({
dollars: this.state.dollars + 20
});
console.log('State after (mouseleave): ' + JSON.stringify(this.state));
}
_onTimeoutHandler() {
console.log('State before (timeout): ' + JSON.stringify(this.state));
this.setState({
dollars: this.state.dollars + 30
});
console.log('State after (timeout): ' + JSON.stringify(this.state));
}
_onAjaxCallback(response) {
if (response.status !== 200) {
console.log('Error in AJAX call: ' + response.statusText);
return;
}
console.log('State before (AJAX call): ' + JSON.stringify(this.state));
this.setState({
dollars: this.state.dollars + 40
});
console.log('State after (AJAX call): ' + JSON.stringify(this.state));
}
};
// Render to DOM
ReactDOM.render(
<TestComponent />,
document.getElementById('app')
);
Каково же решение?
Мы привыкли к вызову setState только с одним параметром, но на самом деле метод может принимать и второй параметр. Второй аргумент, который вы можете передать, - это функция обратного вызова, которая всегда будет выполняться после обновления состояния( независимо от того, находится ли он в известном контексте Реакта или вне его)
К примеру:
_onClickHandler: function _onClickHandler() {
console.log('Cостояние до (_onClickHandler): ' + JSON.stringify(this.state));
this.setState({
dollars: this.state.dollars + 10
}, () => {
console.log('Здесь состояние всегда будет обновляться до последней версии!');
console.log('Состояние после (_onClickHandler): ' + JSON.stringify(this.state));
});
}
Замечание об асинхронной природе setstate
Чтобы быть полит. корректным, setState, как метод, всегда синхронный. Это просто функция, которая вызывает что-то за кулисами - enqueueState или enqueueCallback на updater.
Фактически, вот setState, взятый непосредственно из исходного кода React:
ReactComponent.prototype.setState = function(partialState, callback) {
invariant(
typeof partialState === 'object' ||
typeof partialState === 'function' ||
partialState == null,
'setState(...): takes an object of state variables to update or a ' +
'function which returns an object of state variables.'
);
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
Фактически синхронность или асинхронность - это эффекты вызова setState в приложении React - алгоритм согласования, выполненный сравнением VDOM (виртуального дома) и вызовом рендеринга для обновления реального DOM.