新聞中心
本文轉(zhuǎn)載自微信公眾號「勾勾的前端世界」,作者西嶺。轉(zhuǎn)載本文請聯(lián)系勾勾的前端世界公眾號。

創(chuàng)新互聯(lián)主營成華網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營網(wǎng)站建設(shè)方案,重慶APP開發(fā),成華h5成都微信小程序搭建,成華網(wǎng)站營銷推廣歡迎成華等地區(qū)企業(yè)咨詢
這一次,我將帶你一次性搞懂 React 中常見的 setState 原理。
setState 本身的默認行為
在進入主題之前,你肯定需要先學(xué)會 React 的基本使用。如果不會,請點贊離開;如果會用 React ,那就點贊收藏后離開(●'?'●)。
我們在使用 React 的時候,經(jīng)常會用到 state(一句廢話),但是真正能完全搞清楚 setState 的帥哥美女,確實沒幾個。畢竟程序員都不太可能像我一樣博學(xué)(和好看)。那么,要搞清楚它,應(yīng)該去投胎(整容)嗎?
不,你需要先搞清楚 setState 本身的默認行為。
其實也很簡單,我們都知道,setState可以傳遞對象形式的狀態(tài),也可以傳遞函數(shù)形式的狀態(tài)。而不論狀態(tài)是對象形式還是函數(shù)形式,它都會先將所有狀態(tài)保存起來,然后進行狀態(tài)合并,所有狀態(tài)合并完成后再進行一次性 DOM 更新。
如果狀態(tài)是對象形式,后面的狀態(tài)會直接覆蓋前面的狀態(tài)。類似于 Object.assign() 的合并操作。
對于對象狀態(tài)這一點,我們有請翠花,上代碼:
運行代碼,Dom 中展示的結(jié)果為 1。很顯然兩次 setState 只有一次生效了。
真的嗎?其實兩次都有生效,只不過這兩次 setState 在執(zhí)行前,被合并成了一個。你不能說到底是那個生效,你可以說兩個都沒生效,因為最終執(zhí)行的是被合并的那個代碼。
如果狀態(tài)是函數(shù)形式,那么依次調(diào)用函數(shù)進行狀態(tài)累積,所有函數(shù)調(diào)用完成后, 得到最終狀態(tài),最終進行一次性 DOM 更新。
翠花,再來一段代碼……
明顯不一樣的結(jié)果就能說明,兩次都執(zhí)行了,因為函數(shù)狀態(tài)并不會合并,而是以此運行。
好了,翠花可以先下去休息了,前置只是我們已經(jīng)梳理完了,那么,對于 setState 的研究就結(jié)束了嗎?當(dāng)然不是,接下來,讓我們換個場子,繼續(xù)掰滔(battle)。
setState 同步 OR 異步
在面試場景中,只要和 React 相關(guān),面試官一定舔著臉問你:“ 寶子,setState 是同步還是異步的呀 ” 。
面對這樣的無恥刁難,我們需要先明確,從 API 層面上說,它就是普通的調(diào)用執(zhí)行的函數(shù),自然是同步 API 。
因此,這里所說的同步和異步指的是 API 調(diào)用后更新 DOM 是同步還是異步的。
來,我們有請娜塔莎,上代碼……
果然,洋妹子端上來的代碼確實不好消化,通過結(jié)果我們發(fā)現(xiàn),非常奇怪的一個現(xiàn)象:
第一次事件執(zhí)行顯然為異步的,先打印了兩個 0,Dom 隨之改變?yōu)?1 ;
第二次同樣是異步的,但是我們發(fā)現(xiàn)多次執(zhí)行沒效果 (異步?);
而第三次又是同步執(zhí)行的了;
這什么情況,洋妹子給我們下了迷藥嗎?看我葵花寶典戳破它。
先說結(jié)論,首先,同步和異步主要取決于它被調(diào)用的環(huán)境。
- 如果 setState 在 React 能夠控制的范圍被調(diào)用,它就是異步的。
比如合成事件處理函數(shù), 生命周期函數(shù), 此時會進行批量更新, 也就是將狀態(tài)合并后再進行 DOM 更新。
- 如果 setState 在原生 JavaScript 控制的范圍被調(diào)用,它就是同步的。
比如原生事件處理函數(shù)中, 定時器回調(diào)函數(shù)中, Ajax 回調(diào)函數(shù)中, 此時 setState 被調(diào)用后會立即更新 DOM 。
為什么會這樣呢?
其實,我們看到的所謂的 “異步”,是開啟了 “批量更新” 模式的。
批量更新模式可以減少真實 DOM 渲染的次數(shù),所以只要是 React 能夠控制的范圍,出于性能因素考慮,一定是批量更新模式。批量更新會先合并狀態(tài),再一次性做 DOM 更新。
那么假設(shè)沒有批量更新呢?
從生命周期的角度來看,每一次的 setState 都是一個完整的更新流程,這里面就包含了重新渲染 (re-render) 在內(nèi)的很多操作,大體的流程如下:
- shouldComponentUpdate->componentWillUpdate->render->componentDidUpdate;
re-render 本身涉及對 DOM 的操作,它會帶來較大的性能開銷。假如說 “一次 setState 就觸發(fā)一個完整的更新流程” 這個結(jié)論成立,那么每一次 setState的調(diào)用都會觸發(fā)一次 re-render,我們的視圖很可能沒刷新幾次就卡死了,渲染就會出現(xiàn)下面這樣的流程:
因此,setState 異步(或者說是批量更新)的一個重要動機就是避免頻繁的 re-render。
在實際的 React 運行時中,setState 異步的實現(xiàn)方式有點類似于瀏覽器里的 Event-Loop:
每來一個setState,就把它塞進一個隊列里。等時機成熟,再把隊列里的 state 結(jié)果做合并,最后只針對最新的 state 值走一次更新流程。
這個過程,叫作“批量更新”,批量更新的過程正如下面代碼中的箭頭流程圖所示:
只要我們的同步代碼還在執(zhí)行,“進隊列” 這個動作就不會停止。因此就算我們在React 中寫了一個 N 次的 setState 循環(huán),也只是會增加 state 任務(wù)入隊的次數(shù),并不會帶來頻繁的 re-render。當(dāng) N 次調(diào)用結(jié)束后,僅僅是 state 的任務(wù)隊列內(nèi)容發(fā)生了變化, state 本身并不會立刻改變。
為了更好地讓你吃下娜塔莎,哦不對,是娜塔莎端上來的美食,我?guī)湍闶崂砹?setState 的執(zhí)行流程圖:
當(dāng)然,你可能看不懂這個流程圖(是有多笨啊),沒關(guān)系,下面還會有的。
如果為非批量更新模式,調(diào)用多少次 setState 就會渲染多少次真實 DOM,性能較低。
但是我們在某些條件下需要對 JS 控制的區(qū)域?qū)崿F(xiàn)批量更新 ( 異步更新 DOM ) ,那應(yīng)該怎么做呢?
強制批量更新
其實很簡單,我都不好意思說 so easy ,因為這玩意簡直就是 so TM 的 easy 。
我們只需要將代碼包裹在 unstable_batchedUpdates 方法的回調(diào)函數(shù)中就可以實現(xiàn)強制批量更新。
具體使用方式也很簡單,從 react-dom 中引入進來,然后將代碼放入調(diào)用函數(shù)中就可以了。
(翠花和娜塔莎結(jié)婚了,我來給大家上代碼)
截止到現(xiàn)在,我們成就了一對完美的愛情,啊,呸~
網(wǎng)頁標(biāo)題:面試官:“寶子,setState 是同步還是異步的呀?”
文章源于:http://www.dlmjj.cn/article/coiccid.html


咨詢
建站咨詢
