關(guān)于React setState的實現(xiàn)原理(一)

原文連接:https://www.cnblogs.com/jasonlzy/p/8046118.html

前言

首先在學(xué)習(xí)react的時候就對setSate的實現(xiàn)有比較濃厚的興趣挂谍,那么對于下邊的代碼,可以快速回答嗎迷帜?

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">class Root extends React.Component {
constructor(props) {
super(props); this.state = {
count: 0 };
}
componentDidMount() {
let me = this;
me.setState({
count: me.state.count + 1 });
console.log(me.state.count); // 打印
me.setState({
count: me.state.count + 1 });
console.log(me.state.count); // 打印
setTimeout(function(){
me.setState({
count: me.state.count + 1 });
console.log(me.state.count); // 打印
}, 0);
setTimeout(function(){
me.setState({
count: me.state.count + 1 });
console.log(me.state.count); // 打印
}, 0);
}
render() { return ( <h1>{this.state.count}</h1>
)
}
}</pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

這段代碼大家可能在很多地方看見過输吏,結(jié)果是讓你匪夷所思的0权旷,0,2贯溅,3拄氯。 大部分人相信都不知道其中的原因躲查,首先肯定會問:

  • 為什么前兩次為零,而加上setTimeout就可以打印出來坤邪?
  • 為什么setTimeout打印出不同的結(jié)果熙含?

那么請你接下來向下看,我首先說一下Batch Updata(批量更新)艇纺。如下圖:

image

什么事Batch Update

在一些MV*框架中怎静,就是將一段時間內(nèi)對model的修改批量更新到view的機制。比如那前端比較火的React黔衡、vue為例蚓聘。

在React中,我們在componentDidMount生命周期連續(xù)調(diào)用SetState:

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">componentDidMount () { this.setState({ foo: 1 }) this.setState({ foo: 2 }) this.setState({ foo: 3 })
}</pre>

在沒有Batch Update的情況下盟劫,上面的操作會導(dǎo)致三次組件渲染夜牡,但是使用Batch Update機制下時間上只運行了一次渲染。componentDidMount中三次對model的操作被優(yōu)化為一次view更新侣签,

不必要的Vitual Dom計算被忽略塘装,從而提高了框架的效率。

Batch Update的實現(xiàn)

我們想到的可能就是數(shù)據(jù)結(jié)構(gòu)中的棧和隊列影所,比較一下還是使用一個queue來保存update蹦肴,并在合適的時機對這個queue進行flush操作。那么現(xiàn)在有兩個問題:

  1. 什么時候創(chuàng)建這個queue
  2. 什么時候?qū)@個queue進行flush

那么我們要對Reac和Vue的源碼進行分析猴娩,首先React:React中的Batch Update是通過Transaction(事務(wù))來實現(xiàn)的阴幌。在React源碼關(guān)于Transaction的部分可以用一幅畫解釋:

image

Transaction對一個函數(shù)進行包裝,讓React有機會在一個函數(shù)執(zhí)行前和執(zhí)行后運行特定的邏輯卷中,從而完成對整個Batch Update流程的控制矛双。

簡單的說就是在要執(zhí)行的函數(shù)中用事務(wù)包裹起來,在函數(shù)執(zhí)行前加入initialize階段蟆豫,函數(shù)執(zhí)行议忽,最后執(zhí)行close階段。那么Batch Update中

在事件initialize階段十减,一個update queue被創(chuàng)建徙瓶。在事件中調(diào)用setState方法時,狀態(tài)不會被立即調(diào)用嫉称,而是被push進Update queue中。

函數(shù)執(zhí)行結(jié)束調(diào)用事件的close階段灵疮,Update queue會被flush织阅,這事新的狀態(tài)才會被應(yīng)用到組件上并開始后續(xù)的Virtual DOM更新,biff算法來對

model更新震捣。

對比于React荔棉,Vue實現(xiàn)Batch update就簡單多了:直接借助JS中的Event Loop闹炉。(參考阮老師的http://www.ruanyifeng.com/blog/2013/10/event_loop.html

Vue中的核心代碼就僅僅20多行,如下:

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">// https://github.com/vuejs/vue/blob/dev/src/core/observer/scheduler.js#L122-L148 /**

  • Push a watcher into the watcher queue.
  • Jobs with duplicate IDs will be skipped unless it's
  • pushed when the queue is being flushed. */ export function queueWatcher (watcher: Watcher) {
    const id = watcher.id if (has[id] == null) {
    has[id] = true
    if (!flushing) {
    queue.push(watcher)
    } else { // if already flushing, splice the watcher based on its id
    // if already past its id, it will be run next immediately.
    let i = queue.length - 1
    while (i > index && queue[i].id > watcher.id) {
    i-- }
    queue.splice(i + 1, 0, watcher)
    } // queue the flush
    if (!waiting) {
    waiting = true nextTick(flushSchedulerQueue)
    }
    }
    }</pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

當model被修改時润樱,對應(yīng)的watcher會被推入Update queue渣触, 與此同時還會在異步隊列中添加一個task用于flush當前的Update queue。

這樣一來壹若,當前的task中的其他watcher會被推進同一個Update queue中嗅钻。當前task執(zhí)行結(jié)束后,異步隊列下一個task執(zhí)行店展,update queue

會被 flush养篓,并進行后續(xù)的更新操作。

為了讓 flush 動作能在當前 Task 結(jié)束后盡可能早的開始赂蕴,Vue 會優(yōu)先嘗試將任務(wù) micro-task 隊列柳弄,具體來說,在瀏覽器環(huán)境中 Vue 會優(yōu)

先嘗試使用 MutationObserver API 或 Promise概说,如果兩者都不可用碧注,則 fallback 到 setTimeout。

對比兩個框架可以發(fā)現(xiàn) React 基于 Transition 實現(xiàn)的 Batch Query 是一個不依賴語言特性的通用模式糖赔,因此有更穩(wěn)定可控的表現(xiàn)萍丐,但缺點

是無法完全覆蓋所有情況,例如對于如下代碼:

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">componentDidMount () {
setTimeout(_ => { this.setState({ foo: 1 }) this.setState({ foo: 2 }) this.setState({ foo: 3 })
}, 0)
}</pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

由于 setTimeout 的回調(diào)函數(shù)「不受 React 控制」挂捻,其中的 setState 就無法得到優(yōu)化碉纺,最終會導(dǎo)致 render 函數(shù)執(zhí)行三次。

而 Vue 的實現(xiàn)則對語言特性乃至運行環(huán)境有很強的依賴刻撒,但可以更好的覆蓋各種情況:只要是在同一個 task 中的修改都可以進行 Batch Update 優(yōu)化骨田。

總結(jié)一下:

React 在這里的更新和事務(wù)機制使用比較通用的處理方式。

比如默認第一次應(yīng)用初始化的時候是一次事務(wù)的進行声怔,在用戶交互的時候是一次新的事務(wù)開始态贤,會在同一次同步事務(wù)中標記 batchUpdate=true,這樣的做法是不破壞使用者的代碼醋火。

然后如果是 Ajax悠汽,setTimeout 等要離開主線程進行異步操作的時候會脫離當前 UI 的事務(wù),這時候再進入此次處理的時候 batchUpdate=false芥驳,所以才會 setState 幾次就 render 幾次柿冲。

Vue 的策略雖然在機制上雷同,但是從根本上來講是一種延遲的批量更新機制兆旬。

Angular 在這里也處理得很巧妙假抄,利用 zone.js 對 task 進行攔截,對 JS 現(xiàn)有場景進行 AOP,這樣就成功的橋接了代碼宿饱。

React 的事務(wù)是純粹的 IO 模型的適配熏瞄。

那么Batch Update介紹到這里 ,在下一篇我們將參考React源碼來分析setState的實現(xiàn)過程谬以。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末强饮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子为黎,更是在濱河造成了極大的恐慌邮丰,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碍舍,死亡現(xiàn)場離奇詭異柠座,居然都是意外死亡,警方通過查閱死者的電腦和手機片橡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門妈经,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人捧书,你說我怎么就攤上這事吹泡。” “怎么了经瓷?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵爆哑,是天一觀的道長。 經(jīng)常有香客問我舆吮,道長揭朝,這世上最難降的妖魔是什么负甸? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任摔刁,我火速辦了婚禮纺棺,結(jié)果婚禮上瘦赫,老公的妹妹穿的比我還像新娘。我一直安慰自己挺狰,他們只是感情好驻仅,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布挽牢。 她就那樣靜靜地躺著与学,像睡著了一般彤悔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上索守,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天晕窑,我揣著相機與錄音,去河邊找鬼卵佛。 笑死幕屹,一個胖子當著我的面吹牛蓝丙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播望拖,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼挫鸽!你這毒婦竟也來了说敏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤丢郊,失蹤者是張志新(化名)和其女友劉穎盔沫,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體枫匾,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡架诞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了干茉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谴忧。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖角虫,靈堂內(nèi)的尸體忽然破棺而出沾谓,到底是詐尸還是另有隱情,我是刑警寧澤戳鹅,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布均驶,位于F島的核電站,受9級特大地震影響枫虏,放射性物質(zhì)發(fā)生泄漏妇穴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一隶债、第九天 我趴在偏房一處隱蔽的房頂上張望腾它。 院中可真熱鬧,春花似錦燃异、人聲如沸携狭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽逛腿。三九已至,卻和暖如春仅颇,著一層夾襖步出監(jiān)牢的瞬間单默,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工忘瓦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留搁廓,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像境蜕,于是被迫代替她去往敵國和親蝙场。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 個人筆記, 轉(zhuǎn)載請注明轉(zhuǎn)載自 szhshp的第三邊境研究所 Refs and the DOM In the t...
    szhielelp閱讀 1,479評論 0 1
  • 說在前面 關(guān)于 react 的總結(jié)過去半年就一直碎碎念著要搞起來粱年,各(wo)種(tai)原(lan)因(le)售滤。心...
    陳嘻嘻啊閱讀 6,875評論 7 41
  • 前言 這篇文章主要是為了紀錄一些自己對于setState的認識的不斷深入的過程。我覺得這過程對我自己來說很有價值台诗,...
    Srtian閱讀 6,040評論 9 17
  • 前面提過react中的state和props是react組件中的兩大部分完箩,有很多人分不清state和props,這...
    DCbryant閱讀 17,989評論 0 11
  • 今天是小百靈習(xí)唱會拉队。我演唱的是《秘密花園》和《啟航的小水兵》弊知,媽媽把我打扮得很美,大家都夸我是小公主粱快,我的...
    小哈妹閱讀 201評論 0 1