之前写react的时候,踩了几次坑发现setstate之后state不会立刻更新,于是判定setstate就是异步的方法,但是直到有一天,我想立刻拿到更新的state去传参另一个方法的时候,才问自己,为什么setstate是异步的? 准确地说,在React内部机制能检测到的地方, setState就是异步的;在React检测不到的地方,例如setInterval,setTimeout里,setState就是同步更新的。
1.setState在react生命周期和合成事件会批量覆盖执行
所谓合成事件就是:react为了解决跨平台,兼容性问题,自己封装了一套事件机制,代理了原生的事件,像在jsx中常见的onClick、onChange这些都是合成事件
举个 当遇到多个setstate调用的时候会提取单次传递setstate对象,就像Object.assign的对象合并,相同最后一个key会覆盖前面的key
const a = {name : 'kong', age : '17'}
const b = {name : 'fang', sex : 'men'}
Object.assign({}, a, b);
//{name : 'fang', age : '17', sex : 'men'}
2.setstate在原生事件,setTimeout,setInterval,promise等异步操作中,state会同步更新
当执行到 setTimeout 的时候,把它丢到列队里,并没有去执行,而是先执行的 finally 主进程代码块,等 finally 执行完了, isBatchingUpdates 又变为了 false ,导致最后去执行队列里的 setState 时候, requestWork 走的是和原生事件一样的 expirationTime === Sync if分支,所以表现就会和原生事件一样,可以同步拿到最新的state的值。
3.setState批量更新的过程
在react生命周期和合成事件执行前后都有相应的钩子,分别是pre钩子和post钩子,pre钩子会调用batchedUpdate方法将isBatchingUpdates变量置为true,开启批量更新,而post钩子会将isBatchingUpdates置为false
一、setstate方法
ReactComponent.prototype.setState = function (partialState, callback) {
// 将setState事务放进队列中
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
这里的partialState会产生新的state以一种Object.assgine()的方式跟旧的state进行合并。
二、enqueueSetState
enqueueSetState: function (publicInstance, partialState) {
// 获取当前组件的instance
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
// 将要更新的state放入一个数组里
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
queue.push(partialState);
// 将要更新的component instance也放在一个队列里
enqueueUpdate(internalInstance);
}
三、enqueueUpdate
function enqueueUpdate(component) {
// 如果没有处于批量创建/更新组件的阶段,则处理update state事务
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
// 如果正处于批量创建/更新组件的过程,将当前的组件放在dirtyComponents数组中
dirtyComponents.push(component);
}
isBatchingUpdates变量置为true,则会走批量更新分支,setState的更新会被存入队列中,待同步代码执行完后,再执行队列中的state更新。 isBatchingUpdates 为 true,则把当前组件(即调用了 setState 的组件)放入 dirtyComponents 数组中;否则 batchUpdate 所有队列中的更新
四、batchingStrategy
var ReactDefaultBatchingStrategy = {
// 用于标记当前是否出于批量更新
isBatchingUpdates: false,
// 当调用这个方法时,正式开始批量更新
batchedUpdates: function (callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
// 如果当前事务正在更新过程在中,则调用callback,既enqueueUpdate
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
// 否则执行更新事务
return transaction.perform(callback, null, a, b, c, d, e);
}
}
};
五、事务
batchedUpdates 发起 transaction.perform() 事务
这个事务流程中的 anyMethod 是 runBatchedUpdates ,即更新组件状态并走一遍组件生命周期,在 componentWillMount 时将 A 状态队列里累积的状态都依次处理了。
wrapper close,循环遍历 dirtyComponents 并执行 transaction.perform(runBatchedUpdates, null, transaction);,于是 A 就被捡起来开始走事务流程
直到 dirtyComponents 里最后一个组件跑完流程,组件都被统一更新了一遍。
4.最后来一个测验
(1)
22在33的前面, async函数返回的也是一个promise对象,对!就是返回一个promise,所以promise是一个异步方法,里面的环境里,setstate是同步的
(2)前几天自己忽然写出来个自己都不确定的执行顺序(写bug。。。)
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false
}
}
componentWillMount() {
// 获取业务数据
}
onClick = () => {
setTimeout(() => {this.setState({ loading: true }, () => {console.log('1-setState');})}, 4000)
new Promise(resolve => {
this.setState({ loading: false }, () => {console.log('3-setState');});
console.log('2');
resolve(4);
})
.then((res) => {
this.setState({ loading: false }, () => {console.log('2-setState');});
console.log(res, '////res');
});
console.log('1');
}
render() {
return ;
}
}
export default App;
promise里面的then方法源码是将传入的方法包在一个setTimeout里面,为了立即执行then方法return出来promise去做递归 因此这个环境下setstate是同步的 所以
2-setstate
早于下面一行的console.log(res, '////res');
有兴趣的同学可以研究一下本人梳理并注释的promise源码
https://github.com/xinqiymsz/promise/blob/master/promise.js