在介紹這個問題之前,我們先來看一下一個例子:
state = {
number:1
};
componentDidMount(){
this.setState({number:3})
console.log(this.state.number)
}
看完這個例子,也許很多小伙伴會下意識的以為setState是一個異步方法,但是其實setState並沒有異步的說法,之所以會有一種異步方法的表現形式,歸根結底還是因為react框架本身的性能機制所導致的。因為每次調用setState都會觸發更新,異步操作是為了提高性能,將多個狀態合並一起更新,減少re-render調用。
試想一下如果在組件中有以下這樣一段代碼執行:
for ( let i = 0; i < 100; i++ ) {
this.setState( { num: this.state.num + 1 } );
}
如果setState是一個同步執行的機制,那么這個組件會被重新渲染100次,這對性能是一個相當大的消耗。
顯然,React也是想到了這個問題,因此對setState做了一些特殊的優化:
❝ React會將多個setState的調用合並為一個來執行,也就是說,當執行setState的時候,state中的數據並不會馬上更新
❞
這也很好的印證了剛才提到的那個例子。
但是往往在實際的開發工作中,我們可能需要同步的獲取到更新之后的數據,那么怎么獲取呢?下面介紹幾種常用的方法:
回調函數
setState提供了一個回調函數供開發者使用,在回調函數中,我們可以實時的獲取到更新之后的數據。還是以剛才的例子做示范:
state = {
number:1
};
componentDidMount(){
this.setState({number:3},()=>{
console.log(this.state.number)
})
}
這個時候大家可以看到控制台打印的數據就是最新的了,我們也就實時的獲取到了最新的數據。
setTimeout
上面我們講到了,setState本身並不是一個異步方法,其之所以會表現出一種異步的形式,是因為react框架本身的一個性能優化機制。那么基於這一點,如果我們能夠越過react的機制,是不是就可以令setState以同步的形式體現了呢?
說再多文字不如代碼實踐,實踐才是檢驗真理的唯一標准,下面我們還是以之前的例子為基礎改造一下代碼:
state = {
number:1
};
componentDidMount(){
setTimeout(()=>{
this.setState({number:3})
console.log(this.state.number)
},0)
}
可以看見此時控制台打印的數據是最新的數據。這也完美的印證了我們的猜想是正確的。
原生事件中修改狀態
上面已經印證了避過react的機制,可以同步獲取到更新之后的數據,那么除了setTimeout以外,還有在原生事件中也是可以的。還是看一下例子:
state = {
number:1
};
componentDidMount() {
document.body.addEventListener('click', this.changeVal, false);
}
changeVal = () => {
this.setState({
number: 3
})
console.log(this.state.number)
}
經過實踐,同樣這種方法也是可行的。
總結:
❝ setState本身並不是異步,只是因為react的性能優化機制體現為異步。在react的生命周期函數或者作用域下為異步,在原生的環境下為同步。
❞
-
setState 只在合成事件和鈎子函數中是“異步”的,在原生事件和 setTimeout 中都是同步的。
-
合成事件:就是react 在組件中的onClick等都是屬於它自定義的合成事件
-
原生事件:比如通過addeventListener添加的,dom中的原生事件
-
-
setState的“異步”並不是說內部由異步代碼實現,其實本身執行的過程和代碼都是同步的,只是合成事件和鈎子函數的調用順序在更新之前,導致在合成事件和鈎子函數中沒法立馬拿到更新后的值,形式了所謂的“異步”,當然可以通過第二個參數 setState(partialState, callback) 中的callback拿到更新后的結果。
-
setState 的批量更新優化也是建立在“異步”(合成事件、鈎子函數)之上的,在原生事件和setTimeout 中不會批量更新,在“異步”中如果對同一個值進行多次 setState , setState 的批量更新策略會對其進行覆蓋,取最后一次的執行,如果是同時 setState 多個不同的值,在更新時會對其進行合並批量更新。
state = { val: 0 } batchUpdates = () => { this.setState({ val: this.state.val + 1 }) this.setState({ val: this.state.val + 1 }) this.setState({ val: this.state.val + 1 }) } hooks
const [val,setVal] = useState(0);
setVal(val=>val+1);
setVal(val=>val+1);
setVal(val=>val+1);
-
-
其實val只是1,第二個是3
-
具體大家可以看看react的源碼~
-