在學習react的過程當中幾乎全部學習材料都會反覆強調一點setState是異步的,來看一下react官網對於setState的說明。react
將
setState()
認爲是一次請求而不是一次當即執行更新組件的命令。爲了更爲可觀的性能,React可能會推遲它,稍後會一次性更新這些組件。React不會保證在setState以後,可以馬上拿到改變的結果。數據庫
一個很經典的例子以下異步
// state.count 當前爲 0
componentDidMount(){
this.setState({count: state.count + 1});
console.log(this.state.count)
}
複製代碼
若是你熟悉react,你必定知道最後的輸出結果是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中方法更新stateui
在點擊組件後,你能夠猜到結果嗎?輸出結果是:this
timeout
"prev state:"
0
"next state:"
1
mousedown
"prev state:"
1
"next state:"
2
click
"prev state:"
2
"next state:"
2
複製代碼
codesandbox。spa
結果彷佛有點出人意料,三種方式只有在div上綁定的onClick事件輸出了能夠證實setState是異步的結果,另外兩種方式顯示setState彷佛是同步的。prototype
React的核心成員Dan Abramov也在一次回覆中提到
這究竟是這麼回事?
話很少說,直接上源碼,若是你對react源碼有必定了解能夠接着往下看,若是沒有,能夠直接跳到結論(如下分析基於react15,16版本可能有出入)。
//代碼位於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
方法將傳入的state放到一個隊列中,接下來,看下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
中先是找到需渲染組件並將新的state併入該組件的需更新的state隊列中,接下來調用了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,顧名思義就是批量更新策略,其中經過事務的方式實現state的批量更新,這裏的事務和數據庫中的事務的概念相似,但不徹底相同,這裏就不具體展開了,也是react中頗有意思的內容。 isBatchingUpdates是該事務的一個標誌,若是爲true,表示react正在一個更新組件的事務流中,根據以上代碼邏輯:
那麼在事件中調用setState
又爲何也是異步的呢,react是經過合成事件實現了對於事件的綁定,在組件建立和更新的入口方法mountComponent和updateComponent中會將綁定的事件註冊到document節點上,相應的回調函數經過EventPluginHub存儲。 當事件觸發時,document上addEventListener註冊的callback會被回調,回調函數爲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流程,即所謂的異步。
在回過頭來看同步的狀況,原生事件綁定不會經過合成事件的方式處理,天然也不會進入更新事務的處理流程。setTimeout也同樣,在setTimeout回調執行時已經完成了原更新組件流程,不會放入dirtyComponent進行異步更新,其結果天然是同步的。
順便提一下,在更新組建時,將更新的state合併到原state是在componentWillUpdate以後,render以前,因此在componentWillUpdate以前設置的setState
能夠在render中拿到最新值。
1.在組件生命週期中或者react事件綁定中,setState是經過異步更新的。 2.在延時的回調或者原生事件綁定的回調中調用setState不必定是異步的。
這個結果並不說明setState異步執行的說法是錯誤的,更加準確的說法應該是setState不能保證同步執行。
Dan Abramov也屢次提到從此會將setState完全改造爲異步的,從js conf中提到的suspend新特新也印證了這一點。