В процессе изучения react почти во всех учебных материалах неоднократно будет подчеркиваться, что setState является асинхронным, давайте взглянем на описание setState на официальном сайте react.
Буду
setState()
Думайте об этом как о запросе, а не о немедленном выполнении команды для обновления компонента. Для более впечатляющей производительности React может отложить это и обновить эти компоненты за один раз позже. React не гарантирует, что результат изменения будет доступен сразу после setState.
Очень классический пример выглядит следующим образом.
// state.count 当前为 0
componentDidMount(){
this.setState({count: state.count + 1});
console.log(this.state.count)
}
Если вы знакомы с реагированием, вы должны знать, что конечный результат равен 0, а не 1.
Но так ли это на самом деле?
Давайте посмотрим на другой пример
class Hello extends Component {
constructor(props) {
super(props);
this.state = { counter: 0 };
}
render() {
return <div onClick={this.onClick.bind(this)}>点我</div>;
}
componentDidMount() {
//手动绑定mousedown事件
ReactDom.findDOMNode(this).addEventListener(
"mousedown",
this.onClick.bind(this)
);
//延时调用onclick事件
setTimeout(this.onClick.bind(this), 1000);
}
onClick(event) {
if (event) {
console.log(event.type);
} else {
console.log("timeout");
}
console.log("prev state:", this.state.counter);
this.setState({
counter: this.state.counter + 1
});
console.log("next state:", this.state.counter);
}
}
export default Hello;
Используйте 3 метода для обновления состояния в этом компоненте
- Привязать событие onClick в узле div
- Вручную привязать событие mousedown в componentDidMount
- вызов onClick с setTimeout в компонентеDidMount
Можете ли вы угадать результат после нажатия на компонент? Результат:
timeout
"prev state:"
0
"next state:"
1
mousedown
"prev state:"
1
"next state:"
2
click
"prev state:"
2
"next state:"
2
Результаты кажутся немного неожиданными, только три способа события onClick, привязанного к div, выводят результат, который доказывает, что setState является асинхронным, два других способа показывают, что setState выглядит синхронным.
Основной участник React Дэн Абрамов также упомянул в ответе
Что, черт возьми, здесь происходит?
Нечего сказать, перейдите непосредственно к исходному коду. Если у вас есть определенное понимание исходного кода реакции, вы можете продолжить его чтение. Если нет, вы можете сразу перейти к заключению (следующий анализ основан на реакции 15, а 16 версия может быть другой).
Асинхронная реализация setState
вызов setState в componentWillMount
//代码位于ReactBaseClasses
* @param {partialState} 设置的state参数
* @param {callback} 设置state后的回调
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
называется вenqueueSetState
Метод помещает входящее состояние в очередь, далее смотритеenqueueSetState
Конкретная реализация:
//代码位于ReactUpdateQueue.js
* @param {publicInstance} 需要重新渲染的组件实例
* @param {partialState} 设置的state
* @internal
enqueueSetState: function(publicInstance, partialState) {
//省略部分代码
//从组件列表中找到并返回需渲染的组件
var internalInstance = getInternalInstanceReadyForUpdate(
publicInstance,
'setState',
);
if (!internalInstance) {
return;
}
//state队列
var queue =
internalInstance._pendingStateQueue ||
(internalInstance._pendingStateQueue = []);
//将新的state放入队列
queue.push(partialState);
enqueueUpdate(internalInstance);
},
существуетenqueueSetState
Первый — найти компонент для рендеринга и включить новое состояние в очередь состояний компонента для обновления, а затем вызватьenqueueUpdate
метод, затем посмотрите на:
//代码位于ReactUpdateQueue.js
function enqueueUpdate(internalInstance) {
ReactUpdates.enqueueUpdate(internalInstance);
}
//代码位于ReactUpdates.js
function enqueueUpdate(component) {
ensureInjected();
// Various parts of our code (such as ReactCompositeComponent's
// _renderValidatedComponent) assume that calls to render aren't nested;
// verify that that's the case. (This is called by each top-level update
// function, like setState, forceUpdate, etc.; creation and
// destruction of top-level components is guarded in ReactMount.)
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
}
Этот код является реализациейsetState
Ключом к асинхронному обновлению является batchingStrategy. Как следует из названия, это стратегия пакетного обновления, в которой состояние обновляется пакетами посредством транзакций. Концепции транзакций здесь аналогичны концепциям в базе данных, но они не совсем То же самое Конкретное расширение также является очень интересным контентом в реакции.
isBatchingUpdates — это флаг транзакции. Если он равен true, это означает, что React находится в потоке транзакций обновления компонентов. Согласно приведенной выше логике кода:
- Если его нет в потоке транзакций, вызовите метод batchedUpdates, чтобы войти в процесс обновления.После входа в процесс для isBatchingUpdates будет установлено значение true.
- В противном случае компоненты, которые нужно обновить, положить в dirtyComponents, это тоже очень понятно, сначала сохранить компоненты, которые нужно обновить, а потом обновить. Это объясняет, что вызов setState в componentDidMount не приведет к немедленному обновлению состояния, потому что оно находится в процессе обновления, а isBatchingUpdates имеет значение true, поэтому оно будет помещено только в dirtyComponents и будет ждать более позднего обновления.
Вызов setState в событии
затем позвоните в событиеsetState
А почему он асинхронный?React реализует привязку событий путем синтеза событий.В входных методах mountComponent и updateComponent создания и обновления компонента привязанные события регистрируются на узле документа, а соответствующая callback-функция передается через хранилище EventPluginHub.
Когда событие инициируется, будет вызван обратный вызов, зарегистрированный addEventListener в документе.Функция обратного вызова — ReactEventListener.dispatchEvent, которая является методом входа для распределения события. Давайте посмотрим на dispatchEvent:
dispatchEvent: function (topLevelType, nativeEvent) {
// disable了则直接不回调相关方法
if (!ReactEventListener._enabled) {
return;
}
var bookKeeping = TopLevelCallbackBookKeeping.getPooled(topLevelType, nativeEvent);
try {
// 放入
ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
} finally {
TopLevelCallbackBookKeeping.release(bookKeeping);
}
}
Я видел знакомый метод batchedUpdates, но вызывающий изменил его на ReactUpdates, а затем ввел ReactUpdates.batchedUpdates.
function batchedUpdates(callback, a, b, c, d, e) {
ensureInjected();
return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
}
Внезапно оказывается, что обработка события также завершается через эту же транзакцию.После входа в процесс обработки события isBatchingUpdates транзакции имеет значение true.Если в событии вызывается метод setState, то он также войдет в процесс dirtyComponent, который является так называемым асинхронным.
Собственная привязка событий и setState в setTimeout
Оглядываясь назад на ситуацию с синхронизацией, привязка собственного события не будет обрабатываться синтетическими событиями и, естественно, не войдет в поток обработки транзакции обновления. То же самое верно и для setTimeout.Когда выполняется обратный вызов setTimeout, исходный процесс обновления компонента завершен, и он не будет помещен в dirtyComponent для асинхронного обновления.Результат, естественно, синхронный.
Кстати, при обновлении композиции слияние обновленного состояния с исходным состоянием происходит после componentWillUpdate и до рендера, поэтому ставится перед componentWillUpdatesetState
Вы можете получить последнее значение в рендере.
Суммировать
1. В жизненном цикле компонента или привязке события реакции setState обновляется асинхронно. 2. Вызов setState в отложенном обратном вызове или собственном обратном вызове, привязанном к событию, не обязательно является асинхронным.
Этот результат не означает, что утверждение о том, что setState выполняется асинхронно, неверно. Более точным утверждением должно быть то, что setState не может гарантировать синхронное выполнение.
Дэн Абрамов также много раз упоминал, что setState в будущем будет полностью преобразован в асинхронный, что также подтверждается новой приостановкой, упомянутой в js conf.