5.state更新流程(setState里到底發(fā)生了什么)

人人都能讀懂的react源碼解析(大廠高薪必備)

5.state更新流程(setState里到底發(fā)生了什么)

視頻課程&調(diào)試demos

視頻課程的目的是為了快速掌握react源碼運(yùn)行的過(guò)程和react中的scheduler、reconciler、renderer、fiber等,并且詳細(xì)debug源碼和分析,過(guò)程更清晰。

視頻課程&調(diào)試demos

視頻課程的目的是為了快速掌握react源碼運(yùn)行的過(guò)程和react中的scheduler、reconciler、renderer、fiber等,并且詳細(xì)debug源碼和分析,過(guò)程更清晰。

視頻課程:進(jìn)入課程

demos:demo

課程結(jié)構(gòu):

  1. 開(kāi)篇(聽(tīng)說(shuō)你還在艱難的啃react源碼)
  2. react心智模型(來(lái)來(lái)來(lái),讓大腦有react思維吧)
  3. Fiber(我是在內(nèi)存中的dom)
  4. 從legacy或concurrent開(kāi)始(從入口開(kāi)始,然后讓我們奔向未來(lái))
  5. state更新流程(setState里到底發(fā)生了什么)
  6. render階段(厲害了,我有創(chuàng)建Fiber的技能)
  7. commit階段(聽(tīng)說(shuō)renderer幫我們打好標(biāo)記了,映射真實(shí)節(jié)點(diǎn)吧)
  8. diff算法(媽媽再也不擔(dān)心我的diff面試了)
  9. hooks源碼(想知道Function Component是怎樣保存狀態(tài)的嘛)
  10. scheduler&lane模型(來(lái)看看任務(wù)是暫停、繼續(xù)和插隊(duì)的)
  11. concurrent mode(并發(fā)模式是什么樣的)
  12. 手寫迷你react(短小精悍就是我)

上一節(jié)我們介紹了react兩種模式的入口函數(shù)到render階段的調(diào)用過(guò)程,也就是mount首次渲染的流程,這節(jié)我們介紹在更新?tīng)顟B(tài)之后到render階段的流程。

在react中觸發(fā)狀態(tài)更新的幾種方式:

  • ReactDOM.render

  • this.setState

  • this.forceUpdate

  • useState

  • useReducer

    我們重點(diǎn)看下重點(diǎn)看下this.setState和this.forceUpdate,hook在第11章講

    1.this.setState內(nèi)調(diào)用this.updater.enqueueSetState

    Component.prototype.setState = function (partialState, callback) {
      if (!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null)) {
        {
          throw Error( "setState(...): takes an object of state variables to update or a function which returns an object of state variables." );
        }
      }
      this.updater.enqueueSetState(this, partialState, callback, 'setState');
    };
    

    2.this.forceUpdate和this.setState一樣,只是會(huì)讓tag賦值ForceUpdate

    enqueueForceUpdate(inst, callback) {
        const fiber = getInstance(inst);
        const eventTime = requestEventTime();
        const suspenseConfig = requestCurrentSuspenseConfig();
        const lane = requestUpdateLane(fiber, suspenseConfig);
    
        const update = createUpdate(eventTime, lane, suspenseConfig);
        
        //tag賦值ForceUpdate
        update.tag = ForceUpdate;
        
        if (callback !== undefined && callback !== null) {
          update.callback = callback;
        }
        
        enqueueUpdate(fiber, update);
        scheduleUpdateOnFiber(fiber, lane, eventTime);
    
      },
    };
    

    如果標(biāo)記ForceUpdate,render階段組件更新會(huì)根據(jù)checkHasForceUpdateAfterProcessing,和checkShouldComponentUpdate來(lái)判斷,如果Update的tag是ForceUpdate,則checkHasForceUpdateAfterProcessing為true,當(dāng)組件是PureComponent時(shí),checkShouldComponentUpdate會(huì)淺比較state和props,所以當(dāng)使用this.forceUpdate一定會(huì)更新

const shouldUpdate =
  checkHasForceUpdateAfterProcessing() ||
  checkShouldComponentUpdate(
    workInProgress,
    ctor,
    oldProps,
    newProps,
    oldState,
    newState,
    nextContext,
  );

3.enqueueForceUpdate之后會(huì)經(jīng)歷創(chuàng)建update,調(diào)度update等過(guò)程,接下來(lái)就來(lái)講這些過(guò)程

enqueueSetState(inst, payload, callback) {
  const fiber = getInstance(inst);//fiber實(shí)例

  const eventTime = requestEventTime();
  const suspenseConfig = requestCurrentSuspenseConfig();
  
  const lane = requestUpdateLane(fiber, suspenseConfig);//優(yōu)先級(jí)

  const update = createUpdate(eventTime, lane, suspenseConfig);//創(chuàng)建update

  update.payload = payload;

  if (callback !== undefined && callback !== null) {  //賦值回調(diào)
    update.callback = callback;
  }

  enqueueUpdate(fiber, update);//update加入updateQueue
  scheduleUpdateOnFiber(fiber, lane, eventTime);//調(diào)度update
}

狀態(tài)更新整體流程

_18

創(chuàng)建Update

HostRoot或者ClassComponent觸發(fā)更新后,會(huì)在函數(shù)createUpdate中創(chuàng)建update,并在后面的render階段的beginWork中計(jì)算Update。FunctionComponent對(duì)應(yīng)的Update在第11章講,它和HostRoot或者ClassComponent的Update結(jié)構(gòu)有些不一樣
export function createUpdate(eventTime: number, lane: Lane): Update<*> {//創(chuàng)建update
  const update: Update<*> = {
    eventTime,
    lane,

    tag: UpdateState,
    payload: null,
    callback: null,

    next: null,
  };
  return update;
}

我們主要關(guān)注這些參數(shù):

  • lane:優(yōu)先級(jí)(第12章講)

  • tag:更新的類型,例如UpdateState、ReplaceState

  • payload:ClassComponent的payload是setState第一個(gè)參數(shù),HostRoot的payload是ReactDOM.render的第一個(gè)參數(shù)

  • callback:setState的第二個(gè)參數(shù)

  • next:連接下一個(gè)Update形成一個(gè)鏈表,例如同時(shí)觸發(fā)多個(gè)setState時(shí)會(huì)形成多個(gè)Update,然后用next 連接

updateQueue:

對(duì)于HostRoot或者ClassComponent會(huì)在mount的時(shí)候使用initializeUpdateQueue創(chuàng)建updateQueue,然后將updateQueue掛載到fiber節(jié)點(diǎn)上
export function initializeUpdateQueue<State>(fiber: Fiber): void {
  const queue: UpdateQueue<State> = {
    baseState: fiber.memoizedState,
    firstBaseUpdate: null,
    lastBaseUpdate: null,
  shared: {
      pending: null,
    },
    effects: null,
  };
fiber.updateQueue = queue;
}
  • baseState:初始state,后面會(huì)基于這個(gè)state,根據(jù)Update計(jì)算新的state

    • firstBaseUpdate、lastBaseUpdate:Update形成的鏈表的頭和尾
    • shared.pending:新產(chǎn)生的update會(huì)以單向環(huán)狀鏈表保存在shared.pending上,計(jì)算state的時(shí)候會(huì)剪開(kāi)這個(gè)環(huán)狀鏈表,并且鏈接在lastBaseUpdate后
    • effects:calback不為null的update

    從fiber節(jié)點(diǎn)向上遍歷到rootFiber

    在markUpdateLaneFromFiberToRoot函數(shù)中會(huì)從觸發(fā)更新的節(jié)點(diǎn)開(kāi)始向上遍歷到rootFiber,遍歷的過(guò)程會(huì)處理節(jié)點(diǎn)的優(yōu)先級(jí)(第12章講)

      function markUpdateLaneFromFiberToRoot(
        sourceFiber: Fiber,
        lane: Lane,
      ): FiberRoot | null {
        sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
        let alternate = sourceFiber.alternate;
        if (alternate !== null) {
          alternate.lanes = mergeLanes(alternate.lanes, lane);
        }
        let node = sourceFiber;
        let parent = sourceFiber.return;
        while (parent !== null) {//從觸發(fā)更新的節(jié)點(diǎn)開(kāi)始向上遍歷到rootFiber
          parent.childLanes = mergeLanes(parent.childLanes, lane);//合并childLanes優(yōu)先級(jí)
          alternate = parent.alternate;
          if (alternate !== null) {
            alternate.childLanes = mergeLanes(alternate.childLanes, lane);
          } else {
          }
          node = parent;
          parent = parent.return;
        }
        if (node.tag === HostRoot) {
          const root: FiberRoot = node.stateNode;
          return root;
        } else {
          return null;
        }
      }
    

調(diào)度

在ensureRootIsScheduled中,scheduleCallback會(huì)以一個(gè)優(yōu)先級(jí)調(diào)度render階段的開(kāi)始函數(shù)performSyncWorkOnRoot或者performConcurrentWorkOnRoot
if (newCallbackPriority === SyncLanePriority) {
  // 任務(wù)已經(jīng)過(guò)期,需要同步執(zhí)行render階段
  newCallbackNode = scheduleSyncCallback(
    performSyncWorkOnRoot.bind(null, root)
  );
} else {
  // 根據(jù)任務(wù)優(yōu)先級(jí)異步執(zhí)行render階段
  var schedulerPriorityLevel = lanePriorityToSchedulerPriority(
    newCallbackPriority
  );
  newCallbackNode = scheduleCallback(
    schedulerPriorityLevel,
    performConcurrentWorkOnRoot.bind(null, root)
  );
}

狀態(tài)更新

classComponent狀態(tài)計(jì)算發(fā)生在processUpdateQueue函數(shù)中,涉及很多鏈表操作,看圖更加直白
  • 初始時(shí)fiber.updateQueue單鏈表上有firstBaseUpdate(update1)和lastBaseUpdate(update2),以next連接

  • fiber.updateQueue.shared環(huán)狀鏈表上有update3和update4,以next連接互相連接

  • 計(jì)算state時(shí),先將fiber.updateQueue.shared環(huán)狀鏈表‘剪開(kāi)’,形成單鏈表,連接在fiber.updateQueue后面形成baseUpdate

  • 然后遍歷按這條鏈表,根據(jù)baseState計(jì)算出memoizedState

_19

帶優(yōu)先級(jí)的狀態(tài)更新

類似git提交,這里的c3意味著高優(yōu)先級(jí)的任務(wù),比如用戶出發(fā)的事件,數(shù)據(jù)請(qǐng)求,同步執(zhí)行的代碼等。
  • 通過(guò)ReactDOM.render創(chuàng)建的應(yīng)用沒(méi)有優(yōu)先級(jí)的概念,類比git提交,相當(dāng)于先commit,然后提交c3

    _20
  • 在concurrent模式下,類似git rebase,先暫存之前的代碼,在master上開(kāi)發(fā),然后rebase到之前的分支上

    優(yōu)先級(jí)是由Scheduler來(lái)調(diào)度的,這里我們只關(guān)心狀態(tài)計(jì)算時(shí)的優(yōu)先級(jí)排序,也就是在函數(shù)processUpdateQueue中發(fā)生的計(jì)算,例如初始時(shí)有c1-c4四個(gè)update,其中c1和c3為高優(yōu)先級(jí)

    • 在第一次render的時(shí)候,低優(yōu)先級(jí)的update會(huì)跳過(guò),所以只有c1和c3加入狀態(tài)的計(jì)算

    • 在第二次render的時(shí)候,會(huì)以第一次中跳過(guò)的update(c2)之前的update(c1)作為baseState,跳過(guò)的update和之后的update(c2,c3,c4)作為baseUpdate重新計(jì)算

      在在concurrent模式下,componentWillMount可能會(huì)執(zhí)行多次,變現(xiàn)和之前的版本不一致

      注意,fiber.updateQueue.shared會(huì)同時(shí)存在于workInprogress Fiber和current Fiber,目的是為了防止高優(yōu)先級(jí)打斷正在進(jìn)行的計(jì)算而導(dǎo)致?tīng)顟B(tài)丟失,這段代碼也是發(fā)生在processUpdateQueue中

_21
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評(píng)論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,538評(píng)論 3 417
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開(kāi)封第一講書人閱讀 176,423評(píng)論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 62,991評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,761評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 55,207評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,419評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,959評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,782評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,983評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,222評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,653評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,901評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,678評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,978評(píng)論 2 374

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