React 16.9.0 React-Dom 16.9.0bash
從下面代碼的運行結果能夠得出以下結論:app
setState
,setState
的批量更新策略會對其進行覆蓋,取最後一次的執行class App extends React.Component {
constructor () {
super();
this.state = {
counter: 0
};
}
componentDidMount() {
// 生命週期中調用
console.log("componentDidMount before: " + this.state.counter);
this.setState({ counter: this.state.counter + 1 });
// 此處不能實時更新
console.log("componentDidMount after: " + this.state.counter);
setTimeout(() => {
// setTimeout中調用
console.log("setTimeout before: " + this.state.counter);
this.setState({ counter: this.state.counter + 1 });
console.log("setTimeout after: " + this.state.counter);
}, 0);
document.getElementById("btn-2").addEventListener("click", this.btn2Click);
}
spanClick = () => {
const { counter } = this.state;
console.log("spanClick before: " + this.state.counter);
this.setState({
counter: counter + 1
})
this.setState({
counter: counter + 2
})
// 此處不能實時更新
console.log("spanClick after: " + this.state.counter);
}
btn2Click = () => {
const { counter } = this.state;
console.log("addEventListener btn2Click before: " + this.state.counter);
this.setState({
counter: counter + 1
})
// 此處能夠實時更新
console.log("addEventListener btn2Click after: " + this.state.counter);
}
render () {
return (
<div className="App">
<span className="btn" onClick={(event) => this.spanClick(event)}>
點擊
</span>
<span className="btn-2" id="btn-2">
點擊2
</span>
</div>
)};
}
// 打印結果。before與after相同即爲「異步」,不然爲同步
// componentDidMount before: 0
// componentDidMount after: 0
// setTimeout before: 1
// setTimeout after: 2
// spanClick before: 2
// spanClick after: 2
// addEventListener btn2Click before: 4
// addEventListener btn2Click after: 5
複製代碼
看過不少篇這樣標題的文章,看到過不少描述不一樣但意思如上的結論,可是仍然有一些疑問:異步
到底是同步更新仍是異步更新,取決於代碼的執行環境。React定義了一個內部變量executionContext
(默認爲NoContext
),在進行合成事件和生命週期處理的時候,會首先給該變量賦值爲DiscreteEventContext
(合成事件)或executionContext &= ~BatchedContext; executionContext |= LegacyUnbatchedContext;
(componentDidMount)。來標記其如今所處的執行環境。post
而在setTimeout以及原生事件中,是脫離了這些執行環境的,executionContext
就是默認值NoContext
;。下圖爲原生事件執行時的截圖ui
在scheduleWork
處理邏輯的時候,若是執行環境不爲NoContext
,則僅僅是將更新放在一個隊列裏面,不進行實際的應用(即調用flushSyncCallbackQueue
)。this
是不是同步更新的,取決於其執行環境。由於setTimeout和原生事件脫離了本來的執行環境,因此其state的更新爲同步更新。spa
那在合成事件和生命週期中,又是何時調用的flushSyncCallbackQueue
,下面代碼中的輸出又是什麼?3d
class App extends React.Component {
constructor () {
super();
this.state = {
counter: 0
};
}
appClick = () => {
console.log('--------appClick---------');
const { counter } = this.state;
console.log(this.state.counter);
this.setState({
counter: counter + 1
})
console.log(this.state.counter); // 會輸出2仍是0?
}
spanClick = () => {
const { counter } = this.state;
this.setState({
counter: counter + 1
})
console.log(this.state.counter);
this.setState({
counter: counter + 2
})
console.log(this.state.counter);
}
render () {
return (
<div className="App" onClick={(event) => this.appClick(event)}>
<span className="btn" onClick={(event) => this.spanClick(event)}>
點擊
</span>
</div>
)};
}
複製代碼
若是對React原生事件有了解,以click
事件爲例,會知道React在處理一次點擊事件時,將全部的回調放在了一個隊列裏面。code
參考 juejin.im/post/5d65e5…component
就是在該隊列執行完畢以後調用的flushSyncCallbackQueue
。所以上面的示例代碼中,全部的打印都爲0
;
其執行過程能夠用以下代碼簡單表示:
var a = 1;
var updateQueue = [];
function setState (payload) {
updateQueue.push(payload);
}
function func () {
updateQueue = [];
try {
// 將本次須要調用的放在一塊兒
setState({a: 1});
console.log(window.a);
setState({a: 3});
} finally {
// 模擬最後一次性提交更新
window.a = updateQueue.reduce((accumulator, currentValue) => {
return currentValue.a || accumulator;
}, window.a)
}
}
// 運行
func()
console.log(window.a);
複製代碼
對生命週期或者合成事件包裹了一層try { // 執行,更新放隊列 } finally { // 更新state }
,最後在finally
中進行的state更新