setState同步还是异步
- setState有时表现出异步,有时表现出同步,在合成事件和钩子函数中表现异步
- setState批量更新优化是建立在合成事件和钩子函数之上的,在原生事件和setTimeout中不会发生异步.(顺序为setState => 钩子函数 => 合并更新状态)
合成事件
- React通过事件冒泡,将事件冒泡到document上面,再统一分发给中间层SyntheticEvent去执行事件
钩子函数
- 钩子函数指生命周期的钩子
setState之后到底发生了什么
setState有两个注意事项:
官网提供:
- 出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用
- 因为 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态
1
2
3
4
5add() {
this.setState({count:count + 1})
this.setState({count:count + 1})
}
add()
- 上述连续两次在点击事件调用setState
图例
执行顺序
第一步
- 首先调用实例的updater上的enqueueSetState
- 目的是让setState入列
1
2
3
4
5
6
7ReactComponent.prototype.setState = function (partialState, callback) {
// 将setState事务放进队列中
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};第二步
- 将更新状态的组件也放入队列中
- 将新的state放进数组里
- 用enqueueUpdate来处理将要更新的实例对象
目前达到的效果
- setState在队列中
- 在这个setState执行过程里 我们将state放入了一个队列 将要更新的组件也放入了一个队列
1
2
3
4
5
6
7
8
9
10
11
12enqueueSetState: function (publicInstance, partialState) {
// 获取当前组件的instance
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
// 将要更新的state放入一个数组里
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
queue.push(partialState);
// 将要更新的component instance也放在一个队列里
enqueueUpdate(internalInstance);
}
第三步
- 调用enqueueUpdate执行更新操作,如果处在更新,那么只是将组件放入脏组件队列中
1
2
3
4
5
6
7
8
9function enqueueUpdate(component) {
// 如果没有处于批量创建/更新组件的阶段,则处理update state事务
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
// 如果正处于批量创建/更新组件的过程,将当前的组件放在dirtyComponents数组中
dirtyComponents.push(component);
} - 这个更新函数,接收一个组件的实例 首先判断是否处于更新阶段 如果不是 那么执行更新 如果处在更新阶段 那么我将组件的实例push到队列中 等待更新
这个函数也说明了setState是一个异步的过程,它会集齐一批需要更新的组件然后一起更新
第三步拓展——批量更新策略(batchingStrategy)
- 第三步中,如果不处在更新状态,就执行
batchingStrategy.batchedUpdates(enqueueUpdate,component)
批量更新策略所在的对象
- batchedUpdates做了个判断,如果我处在更新状态,那么组件入列, 否则开启更新事务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18var ReactDefaultBatchingStrategy = {
// 用于标记当前是否出于批量更新
isBatchingUpdates: false,
// 当调用这个方法时,正式开始批量更新
batchedUpdates: function (callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
// 如果当前事务正在更新过程中,则调用enqueueUpdate
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
// 否则执行更新事务
return transaction.perform(callback, null, a, b, c, d, e);
}
}
};
第三步拓展——事务
所谓事务,就是把函数做一层包装,开始是做一些操作,结束时做一些操作
下面是一个简单的事务例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29var Transaction = require('./Transaction');
// 我们自己定义的 Transaction
var MyTransaction = function() {
// do sth.
};
Object.assign(MyTransaction.prototype, Transaction.Mixin, {
getTransactionWrappers: function() {
return [{
initialize: function() {
console.log('before method perform');
},
close: function() {
console.log('after method perform');
}
}];
};
});
var transaction = new MyTransaction();
var testMethod = function() {
console.log('test');
}
transaction.perform(testMethod);
// before method perform
// test
// after method performReact源码的事务中有两个事务RESET_BATCHED_UPDATES(用于重置更新状态) FLUSH_BATCHED_UPDATES(再发起一个dom的批量更新,包括渲染和虚拟dom比对等等)
FLUSH_BATCHED_UPDATES这个事务的目的在于循环所有更新组件,执行update,调用组件更新的生命周期等等
1
2
3
4
5
6
7
8
9
10
11var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function () {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
}
};
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};
总结
- setState第一步:将state塞入数组,将组件交给更新队列
- setState第二步:更新队列进行更新,如果更新队列正在更新,将组件塞入脏组件队列.否则执行批量更新策略
- setState第三步:批量更新策略判断是否处在批量更新状态,如果正在更新,那么将更新组件塞入脏组件队列,否则把脏组件所有组件拿出来开启事务更新
- setState第四步,批量更新结束事务,在事务结束前,会调用FLUSH_BATCHED_UPDATES计算最新的
state
和props
.然后关闭事务 - 然后React可以拿到最新的state和props进行虚拟dom比对,虚拟dom比对完之后,会去渲染组件,此时如果有shouldUpdate,会进行一个是否渲染组件的判断
为什么setTimeout下setState是同步的
- 关键在于批量更新策略什么时候开启的
- React开启批量更新策略有两个位置,一个是钩子函数,一个是合成事件,可以将钩子函数和合成事件理解为一个大的事务,当触发时,React会开启批量更新策略.而当执行setTimeout的时候,大事务已经关闭,批量更新策略已经重置为false
- 实际上,调用setState触发了两个批量更新事务,相当于同步更新的过程了.
面试问答
- 问: setState过后到底发生了什么?
- 答: 将组件交给更新队列,将state塞入数组,然后执行队列更新方法,如果队列正在更新,组件就塞入脏组件,否则执行批量更新策略, 批量更新策略会去判断是否正在批量更新,如果正在更新,会把队列放入脏组件,否则循环脏组件队列开启事务执行批量更新,然后在事务结束前计算最新state和props,React拿到他们去进行虚拟dom比对,组件渲染,判断shouldUpdate,进行更新.更新完执行componentDidUpdate…
React - setState源码分析(小白可读)
揭密React setState
react源码分析之-setState是异步还是同步?