React中setState同步異步場(chǎng)景的使用
setState同步異步場(chǎng)景
React
通過(guò)this.state
來(lái)訪問(wèn)state
,通過(guò)this.setState()
方法來(lái)更新state
,當(dāng)this.setState()
方法被調(diào)用的時(shí)候,React
會(huì)重新調(diào)用render
方法來(lái)重新渲染UI
。相比較于在使用Hooks
完成組件下所需要的心智負(fù)擔(dān),setState
就是在使用class
完成組件下所需要的心智負(fù)擔(dān),當(dāng)然所謂的心智負(fù)擔(dān)也許叫做所必須的基礎(chǔ)知識(shí)更加合適一些。
描述
setState
只在合成事件和生命周期鉤子函數(shù)中是異步的,而在原生事件中都是同步的,簡(jiǎn)單實(shí)現(xiàn)一個(gè)React Class TS
例子。
import React from "react"; import "./styles.css"; interface Props{} interface State{ count1: number; count2: number; count3: number; } export default class App extends React.Component<Props, State>{ constructor(props: Props){ super(props); this.state = { count1: 0, count2: 0, count3: 0 } } incrementAsync = () => { console.log("incrementAsync before setState", this.state.count1); this.setState({ count1: this.state.count1 + 1 }); console.log("incrementAsync after setState", this.state.count1); } incrementSync = () => { setTimeout(() => { console.log("incrementSync before setState", this.state.count2); this.setState({ count2: this.state.count2 + 1 }); console.log("incrementSync after setState", this.state.count2); },0); } incrementAsyncFn = () => { console.log("incrementAsyncFn before setState", this.state.count3); this.setState({ count3: this.state.count3 + 1 }, () => { console.log("incrementAsyncFn after.1 setState", this.state.count3); } ); this.setState(state => { console.log("incrementAsyncFn after.2 setState", state.count3); return {count3: state.count3} }); } render(){ return <div> <button onClick={this.incrementAsync}>異步</button> <button onClick={this.incrementSync}>同步</button> <button onClick={this.incrementAsyncFn}>異步函數(shù)參數(shù)</button> </div> } }
以此點(diǎn)擊三個(gè)按鈕的話,可以得到以下輸出。
incrementAsync before setState 0
incrementAsync after setState 0
incrementSync before setState 0
incrementSync after setState 1
incrementAsyncFn before setState 0
incrementAsyncFn after.2 setState 1
incrementAsyncFn after.1 setState 1
首先看incrementAsync
的結(jié)果,在這里我們可以看出,在合成事件調(diào)用setState
之后,this.state
是無(wú)法立即得到最新的值。
對(duì)于incrementSync
的結(jié)果,在非合成事件的調(diào)用時(shí),this.state
是可以立即得到最新的值的,例如使用addEventListener
、setTimeout
、setInterval
等。
對(duì)于incrementAsyncFn
的兩個(gè)結(jié)果,首先來(lái)說(shuō)after.2
的結(jié)果,對(duì)于this.state
也是可以得到最新的值的,如果你需要基于當(dāng)前的state
來(lái)計(jì)算出新的值,那么就可以通過(guò)傳遞一個(gè)函數(shù),而不是一個(gè)對(duì)象的方式來(lái)實(shí)現(xiàn),因?yàn)?code>setState的調(diào)用是分批的,所以通過(guò)傳遞函數(shù)可以鏈?zhǔn)降剡M(jìn)行更新,當(dāng)然前提是需要確保它們是一個(gè)建立在另一個(gè)之上的,也就是說(shuō)傳遞函數(shù)的setState
的值是依賴于上次一的SetState
的,對(duì)于after.1
的結(jié)果,setState
函數(shù)的第二個(gè)參數(shù)是一個(gè)回調(diào)函數(shù),在setState
批量更新完成后可以拿到最新的值,而after.2
也是屬于批量更新需要調(diào)用的函數(shù),所以after.1
會(huì)在after.2
后執(zhí)行。
原理
React
將其實(shí)現(xiàn)為異步的動(dòng)機(jī)主要是性能的考量,setState
的異步并不是說(shuō)內(nèi)部由異步代碼實(shí)現(xiàn),其實(shí)本身執(zhí)行的過(guò)程和代碼都是同步的,只是合成事件和生命周期鉤子函數(shù)的調(diào)用順序在批處理更新之前,導(dǎo)致在合成事件和生命周期鉤子函數(shù)中沒法立馬拿到更新后的值,形式了所謂的異步,實(shí)際上是否進(jìn)行批處理是由其內(nèi)部的isBatchingUpdates
的值來(lái)決定的。setState
依賴于合成事件,合成事件指的是React
并不是將click
等事件直接綁定在DOM
上面,而是采用事件冒泡的形式冒泡到頂層DOM
上,類似于事件委托,然后React
將事件封裝給正式的函數(shù)處理運(yùn)行和處理。說(shuō)完了合成事件再回到setState
,setState
的批量更新優(yōu)化也是建立在合成事件上的,其會(huì)將所有的setState
進(jìn)行批處理,如果對(duì)同一個(gè)值進(jìn)行多次 setState
,setState
的批量更新策略會(huì)對(duì)其進(jìn)行覆蓋,取最后一次的執(zhí)行,如果是同時(shí)setState
多個(gè)不同的值,在更新時(shí)也會(huì)對(duì)其進(jìn)行合并批量更新,而在原生事件中,值會(huì)立即進(jìn)行更新。
采用批量更新,簡(jiǎn)單來(lái)說(shuō)就是為了提升性能,因?yàn)椴徊捎门扛?,在每次更新?shù)據(jù)都會(huì)對(duì)組件進(jìn)行重新渲染,舉個(gè)例子,讓我們?cè)谝粋€(gè)方法內(nèi)重復(fù)更新一個(gè)值。
this.setState({ msg: 1 }); this.setState({ msg: 2 }); this.setState({ msg: 3 });
事實(shí)上,我們真正想要的其實(shí)只是最后一次更新而已,也就是說(shuō)前三次更新都是可以省略的,我們只需要等所有狀態(tài)都修改好了之后再進(jìn)行渲染就可以減少一些性能損耗。還有一個(gè)例子,如果更改了N
個(gè)狀態(tài),其實(shí)只需要一次setState
就可以將DOM
更新到最新,如果我們更新多個(gè)值。
this.setState({ msg: 1 }); this.setState({ age: 2 }); this.setState({ name: 3 });
此處我們分三次修改了三種狀態(tài),但其實(shí)React
只需要渲染一次,在setState
批處理之后會(huì)將其合并,并進(jìn)行一次re-render
就可以將整個(gè)組件的DOM
更新到最新,根本不需要關(guān)心這個(gè)setState
到底是從哪個(gè)具體的狀態(tài)發(fā)出來(lái)的。
那么還有一個(gè)問(wèn)題,首先我們可以認(rèn)同進(jìn)行批處理更新對(duì)我們的性能是有益的,例如Child
和Parent
都調(diào)用setState
,我們不需要重新渲染Child
兩次。但是此時(shí)我們可能會(huì)想到一個(gè)問(wèn)題,為什么不能如同Vue
一樣,Vue
是在值更新之后觸發(fā)setter
然后進(jìn)行更新,更新的過(guò)程同樣也是采用異步渲染,也會(huì)將所有觸發(fā)Watcher
的update
進(jìn)行去重合并再去更新視圖,也就是說(shuō)Vue
是立即修改了值,而后再去更新視圖的。也就是說(shuō),相比較于React
,為什么不能在同樣做批處理的情況下,立即將setState
更新寫入this.state
而不等待協(xié)調(diào)結(jié)束。
任何一種解決方案都有權(quán)衡,對(duì)于Vue
來(lái)說(shuō)因?yàn)槠涫峭ㄟ^(guò)劫持了數(shù)據(jù)的setter
過(guò)程,在使用的也是直接使用=
直接賦值的,而在賦值之后進(jìn)行視圖的更新也是一個(gè)自然的過(guò)程,如果類似于React
一樣在=
之后這個(gè)值依然沒有變化,在直覺上是非常不符合常理的,雖然Vue
是通過(guò)劫持setter
實(shí)現(xiàn)的視圖更新,但是做到如同React
一樣并不是不可能的,這是Vue
采用的解決方案上的權(quán)衡,當(dāng)然這只是可能的一個(gè)理由,說(shuō)是問(wèn)題的權(quán)衡,實(shí)際上還是需要對(duì)比React
來(lái)看,對(duì)于React
中要處理的問(wèn)題,Vue
自然會(huì)有自己解決方案的權(quán)衡,歸根到底還是框架的設(shè)計(jì)哲學(xué)的問(wèn)題。對(duì)于上面提出的在同樣做批處理的情況下,立即將setState
更新寫入this.state
而不等待協(xié)調(diào)結(jié)束的這個(gè)問(wèn)題,dan
給予了兩個(gè)理由,在此簡(jiǎn)作總結(jié),完整的英文版本還請(qǐng)看參考中的github issue
。
保證內(nèi)部數(shù)據(jù)統(tǒng)一
即使state
是同步更新的,但props
是不會(huì)的,在重新渲染父組件之前,無(wú)法知道props
,如果同步執(zhí)行此操作,批處理就會(huì)消失?,F(xiàn)在React
提供的對(duì)象state
、props
、refs
在內(nèi)部是一致的。這意味著如果只使用這些對(duì)象,則可以保證它們引用完全協(xié)調(diào)的樹,即使它是該樹的舊版本。當(dāng)僅使用state
時(shí),同步刷新的模式將起作用。
console.log(this.state.value); // 0 this.setState({ value: this.state.value + 1 }); console.log(this.state.value); // 1 this.setState({ value: this.state.value + 1 }); console.log(this.state.value); // 2
但是,假設(shè)需要提升此狀態(tài)以在幾個(gè)組件之間共享,因此將其移動(dòng)到父級(jí),也就是說(shuō)有props
參與到了傳值,那么同步setState
模式就會(huì)有問(wèn)題,此時(shí)將state
提升到了父組件,利用props
將值傳導(dǎo)子組件。
console.log(this.props.value); // 0 this.props.onIncrement(); console.log(this.props.value); // 0 this.props.onIncrement(); console.log(this.props.value); // 0
這是因?yàn)樵谶@個(gè)解決方案中,this.state
會(huì)立即刷新,而this.props
不會(huì),而且我們不能在不重新渲染父對(duì)象的情況下立即刷新this.props
,這意味著我們將不得不放棄批處理的策略。還有更微妙的情況說(shuō)明這如何破壞一致性的,例如這種方案正在混合來(lái)自props
尚未刷新和state
建議立即刷新的數(shù)據(jù)以創(chuàng)建新狀態(tài)。在React
中,this.state
和this.props
都只在協(xié)調(diào)和刷新之后更新,所以你會(huì)在refactoring
之前和之后看到0
被打印出來(lái)。這使得提升狀態(tài)安全。在某些情況下這可能會(huì)帶來(lái)不便,特別是對(duì)于來(lái)自更多OO
背景的人來(lái)說(shuō),他們只想多次改變狀態(tài),而不是考慮如何在一個(gè)地方表示完整的狀態(tài)更新,我可以理解這一點(diǎn),盡管我確實(shí)認(rèn)為從調(diào)試的角度來(lái)看,保持狀態(tài)更新的集中更加清晰??偠灾?code>React模型并不總是會(huì)產(chǎn)生最簡(jiǎn)潔的代碼,但它在內(nèi)部是一致的,并確保提升狀態(tài)是安全的。
啟用并發(fā)更新
從概念上講React
的行為就好像每個(gè)組件都有一個(gè)更新隊(duì)列,我們?cè)谶@里討論是否同步刷新state
有一個(gè)前提那就是我們默認(rèn)更新節(jié)點(diǎn)是遵循特定的順序的,但是按默認(rèn)順序更新組件在以后的react
中可能就變了。對(duì)于現(xiàn)在我們一直在談?wù)摰漠惒戒秩?,我承認(rèn)我們?cè)趥鬟_(dá)這意味著什么方面做得不是很好,但這就是研發(fā)的本質(zhì):你追求一個(gè)在概念上看起來(lái)很有前途的想法,但只有在花了足夠的時(shí)間之后才能真正理解它的含義。
對(duì)于這個(gè)理由,是React
發(fā)展的一個(gè)方向。我們一直在解釋異步渲染的一種方式是React
可以根據(jù)setState()
調(diào)用的來(lái)源分配不同的優(yōu)先級(jí):事件處理程序、網(wǎng)絡(luò)響應(yīng)、動(dòng)畫等。例如你現(xiàn)在正在打字,那么TextBox
組件需要實(shí)時(shí)的刷新,但是當(dāng)你在輸入的時(shí)候,來(lái)了一個(gè)信息,這個(gè)時(shí)候可能讓信息的渲染延遲到某個(gè)閾值,而不是因?yàn)樽枞€程而讓輸入卡頓。如果我們讓某些更新具有較低優(yōu)先級(jí),我們可以將它們的渲染分成幾毫秒的小塊,這樣用戶就不會(huì)注意到它們。異步rendering
不僅僅是性能上的優(yōu)化,我們認(rèn)為這是React
組件模型可以做什么的根本性轉(zhuǎn)變。例如,考慮從一個(gè)屏幕導(dǎo)航到另一個(gè)屏幕的情況,通常會(huì)在渲染新屏幕時(shí)顯示一個(gè)導(dǎo)航器,但是如果導(dǎo)航速度足夠快,閃爍并立即隱藏導(dǎo)航器會(huì)導(dǎo)致用戶體驗(yàn)下降,更糟糕的是如果有多個(gè)級(jí)別的組件具有不同的異步依賴項(xiàng)例如數(shù)據(jù)、代碼、圖像等,您最終會(huì)得到一連串短暫閃爍的導(dǎo)航器。由于所有的DOM
重排,這既在視覺上令人不快,又使您的應(yīng)用程序在實(shí)踐中變慢。如果當(dāng)您執(zhí)行一個(gè)簡(jiǎn)單的setState()
來(lái)呈現(xiàn)不同的視圖時(shí),我們可以開始在后臺(tái)呈現(xiàn)更新后的視圖。如果您自己不編寫任何協(xié)調(diào)代碼,您可以選擇在更新時(shí)間超過(guò)某個(gè)閾值時(shí)顯示導(dǎo)航器,否則當(dāng)整個(gè)新子樹的異步依賴項(xiàng)是時(shí)讓React
執(zhí)行無(wú)縫轉(zhuǎn)換使?jié)M意。請(qǐng)注意,這只是可能的,因?yàn)?code>this.state不會(huì)立即刷新,如果它被立即刷新,我們將無(wú)法開始在后臺(tái)渲染視圖的新版本,而舊版本仍然可見且可交互,他們獨(dú)立的狀態(tài)更新會(huì)發(fā)生沖突。
參考
https://www.jianshu.com/p/cc12e9a8052c
https://juejin.cn/post/6844903636749778958
https://zh-hans.reactjs.org/docs/faq-state.html
https://blog.csdn.net/zz_jesse/article/details/118282921
https://blog.csdn.net/weixin_44874595/article/details/104270057
到此這篇關(guān)于React中setState同步異步場(chǎng)景的使用的文章就介紹到這了,更多相關(guān)React setState同步異步內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React實(shí)現(xiàn)雙滑塊交叉滑動(dòng)
這篇文章主要為大家詳細(xì)介紹了React實(shí)現(xiàn)雙滑塊交叉滑動(dòng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09React?UI組件庫(kù)之快速實(shí)現(xiàn)antd的按需引入和自定義主題
react入門學(xué)習(xí)告一段路,下面這篇文章主要給大家介紹了關(guān)于React?UI組件庫(kù)之快速實(shí)現(xiàn)antd的按需引入和自定義主題的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07React中使用Workbox進(jìn)行預(yù)緩存的實(shí)現(xiàn)代碼
Workbox是Google Chrome團(tuán)隊(duì)推出的一套 PWA 的解決方案,這套解決方案當(dāng)中包含了核心庫(kù)和構(gòu)建工具,因此我們可以利用Workbox實(shí)現(xiàn)Service Worker的快速開發(fā),本文小編給大家介紹了React中使用Workbox進(jìn)行預(yù)緩存的實(shí)現(xiàn),需要的朋友可以參考下2023-11-11使用React+ts實(shí)現(xiàn)無(wú)縫滾動(dòng)的走馬燈詳細(xì)過(guò)程
這篇文章主要給大家介紹了關(guān)于使用React+ts實(shí)現(xiàn)無(wú)縫滾動(dòng)的走馬燈詳細(xì)過(guò)程,文中給出了詳細(xì)的代碼示例以及圖文教程,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2023-08-08React-Native實(shí)現(xiàn)ListView組件之上拉刷新實(shí)例(iOS和Android通用)
本篇文章主要介紹了React-Native實(shí)現(xiàn)ListView組件之上拉刷新實(shí)例(iOS和Android通用),具有一定的參考價(jià)值,有興趣的可以了解一下2017-07-07react-native 完整實(shí)現(xiàn)登錄功能的示例代碼
本篇文章主要介紹了react-native 完整實(shí)現(xiàn)登錄功能的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09React實(shí)現(xiàn)歌詞滾動(dòng)效果(跟隨音樂(lè)播放時(shí)間滾動(dòng))
這篇文章主要為大家詳細(xì)介紹了React實(shí)現(xiàn)歌詞滾動(dòng)效果(跟隨音樂(lè)播放使勁按滾動(dòng)),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2024-02-02React BootStrap用戶體驗(yàn)框架快速上手
這篇文章主要介紹了React BootStrap用戶體驗(yàn)框架快速上手的相關(guān)知識(shí),非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2018-03-03