之前寫react的時候斋泄,踩了幾次坑發(fā)現(xiàn)setstate之后state不會立刻更新,于是判定setstate就是異步的方法镐牺,但是直到有一天炫掐,我想立刻拿到更新的state去傳參另一個方法的時候,才問自己睬涧,為什么setstate是異步的募胃? 準(zhǔn)確地說,在React內(nèi)部機(jī)制能檢測到的地方畦浓, setState就是異步的痹束;在React檢測不到的地方,例如setInterval,setTimeout里讶请,setState就是同步更新的祷嘶。
1.setState在react生命周期和合成事件會批量覆蓋執(zhí)行
所謂合成事件就是:react為了解決跨平臺屎媳,兼容性問題,自己封裝了一套事件機(jī)制论巍,代理了原生的事件烛谊,像在jsx中常見的onClick、onChange這些都是合成事件
舉個?? 當(dāng)遇到多個setstate調(diào)用的時候會提取單次傳遞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會同步更新
當(dāng)執(zhí)行到 setTimeout 的時候双泪,把它丟到列隊里,并沒有去執(zhí)行密似,而是先執(zhí)行的 finally 主進(jìn)程代碼塊焙矛,等 finally 執(zhí)行完了, isBatchingUpdates 又變?yōu)榱?false 辛友,導(dǎo)致最后去執(zhí)行隊列里的 setState 時候薄扁, requestWork 走的是和原生事件一樣的 expirationTime === Sync if分支,所以表現(xiàn)就會和原生事件一樣废累,可以同步拿到最新的state的值邓梅。
3.setState批量更新的過程
在react生命周期和合成事件執(zhí)行前后都有相應(yīng)的鉤子,分別是pre鉤子和post鉤子邑滨,pre鉤子會調(diào)用batchedUpdate方法將isBatchingUpdates變量置為true日缨,開啟批量更新,而post鉤子會將isBatchingUpdates置為false
一掖看、setstate方法
ReactComponent.prototype.setState = function (partialState, callback) {
// 將setState事務(wù)放進(jìn)隊列中
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
這里的partialState會產(chǎn)生新的state以一種Object.assgine()的方式跟舊的state進(jìn)行合并匣距。
二、enqueueSetState
enqueueSetState: function (publicInstance, partialState) {
// 獲取當(dāng)前組件的instance
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
// 將要更新的state放入一個數(shù)組里
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
queue.push(partialState);
// 將要更新的component instance也放在一個隊列里
enqueueUpdate(internalInstance);
}
三哎壳、enqueueUpdate
function enqueueUpdate(component) {
// 如果沒有處于批量創(chuàng)建/更新組件的階段毅待,則處理update state事務(wù)
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
// 如果正處于批量創(chuàng)建/更新組件的過程,將當(dāng)前的組件放在dirtyComponents數(shù)組中
dirtyComponents.push(component);
}
isBatchingUpdates變量置為true归榕,則會走批量更新分支尸红,setState的更新會被存入隊列中,待同步代碼執(zhí)行完后刹泄,再執(zhí)行隊列中的state更新外里。 isBatchingUpdates 為 true,則把當(dāng)前組件(即調(diào)用了 setState 的組件)放入 dirtyComponents 數(shù)組中特石;否則 batchUpdate 所有隊列中的更新
四盅蝗、batchingStrategy
var ReactDefaultBatchingStrategy = {
// 用于標(biāo)記當(dāng)前是否出于批量更新
isBatchingUpdates: false,
// 當(dāng)調(diào)用這個方法時,正式開始批量更新
batchedUpdates: function (callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
// 如果當(dāng)前事務(wù)正在更新過程在中姆蘸,則調(diào)用callback墩莫,既enqueueUpdate
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
// 否則執(zhí)行更新事務(wù)
return transaction.perform(callback, null, a, b, c, d, e);
}
}
};
五芙委、事務(wù)
batchedUpdates 發(fā)起 transaction.perform() 事務(wù)
這個事務(wù)流程中的 anyMethod 是 runBatchedUpdates ,即更新組件狀態(tài)并走一遍組件生命周期贼穆,在 componentWillMount 時將 A 狀態(tài)隊列里累積的狀態(tài)都依次處理了题山。
wrapper close,循環(huán)遍歷 dirtyComponents 并執(zhí)行 transaction.perform(runBatchedUpdates, null, transaction);故痊,于是 A 就被撿起來開始走事務(wù)流程
直到 dirtyComponents 里最后一個組件跑完流程顶瞳,組件都被統(tǒng)一更新了一遍。
4.最后來一個測驗
(1)
22在33的前面愕秫, async函數(shù)返回的也是一個promise對象慨菱,對!就是返回一個promise戴甩,所以promise是一個異步方法符喝,里面的環(huán)境里,setstate是同步的
(2)前幾天自己忽然寫出來個自己都不確定的執(zhí)行順序??(寫bug甜孤。协饲。。)
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false
}
}
componentWillMount() {
// 獲取業(yè)務(wù)數(shù)據(jù)
}
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 <Button type="primary" loading={this.state.loading} onClick={this.onClick}>缴川;all 起來了</Button>;
}
}
export default App;
promise里面的then方法源碼是將傳入的方法包在一個setTimeout里面茉稠,為了立即執(zhí)行then方法return出來promise去做遞歸 因此這個環(huán)境下setstate是同步的 所以
2-setstate
早于下面一行的console.log(res, '////res');
有興趣的同學(xué)可以研究一下本人梳理并注釋的promise源碼
https://github.com/xinqiymsz/promise/blob/master/promise.js