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


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

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

視頻課程&調試demos

​ 視頻課程的目的是為了快速掌握react源碼運行的過程和react中的scheduler、reconciler、renderer、fiber等,並且詳細debug源碼和分析,過程更清晰。

​ 視頻課程:進入課程

​ demos:demo

課程結構:

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

​ 上一節我們介紹了react兩種模式的入口函數到render階段的調用過程,也就是mount首次渲染的流程,這節我們介紹在更新狀態之后到render階段的流程。

在react中觸發狀態更新的幾種方式:

  • ReactDOM.render

  • this.setState

  • this.forceUpdate

  • useState

  • useReducer

    我們重點看下重點看下this.setState和this.forceUpdate,hook在第11章講

    1.this.setState內調用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一樣,只是會讓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);
    
      },
    };
    

    ​ 如果標記ForceUpdate,render階段組件更新會根據checkHasForceUpdateAfterProcessing,和checkShouldComponentUpdate來判斷,如果Update的tag是ForceUpdate,則checkHasForceUpdateAfterProcessing為true,當組件是PureComponent時,checkShouldComponentUpdate會淺比較state和props,所以當使用this.forceUpdate一定會更新

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

    3.enqueueForceUpdate之后會經歷創建update,調度update等過程,接下來就來講這些過程

    enqueueSetState(inst, payload, callback) {
      const fiber = getInstance(inst);//fiber實例
    
      const eventTime = requestEventTime();
      const suspenseConfig = requestCurrentSuspenseConfig();
      
      const lane = requestUpdateLane(fiber, suspenseConfig);//優先級
    
      const update = createUpdate(eventTime, lane, suspenseConfig);//創建update
    
      update.payload = payload;
    
      if (callback !== undefined && callback !== null) {  //賦值回調
        update.callback = callback;
      }
    
      enqueueUpdate(fiber, update);//update加入updateQueue
      scheduleUpdateOnFiber(fiber, lane, eventTime);//調度update
    }
    
    

    狀態更新整體流程

    _18

    創建Update

    ​ HostRoot或者ClassComponent觸發更新后,會在函數createUpdate中創建update,並在后面的render階段的beginWork中計算Update。FunctionComponent對應的Update在第11章講,它和HostRoot或者ClassComponent的Update結構有些不一樣

    export function createUpdate(eventTime: number, lane: Lane): Update<*> {//創建update
      const update: Update<*> = {
        eventTime,
        lane,
    
        tag: UpdateState,
        payload: null,
        callback: null,
    
        next: null,
      };
      return update;
    }
    

    我們主要關注這些參數:

    • lane:優先級(第12章講)

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

    • payload:ClassComponent的payload是setState第一個參數,HostRoot的payload是ReactDOM.render的第一個參數

    • callback:setState的第二個參數

    • next:連接下一個Update形成一個鏈表,例如同時觸發多個setState時會形成多個Update,然后用next 連接

    updateQueue:

    ​ 對於HostRoot或者ClassComponent會在mount的時候使用initializeUpdateQueue創建updateQueue,然后將updateQueue掛載到fiber節點上

    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,后面會基於這個state,根據Update計算新的state

    • firstBaseUpdate、lastBaseUpdate:Update形成的鏈表的頭和尾
    • shared.pending:新產生的update會以單向環狀鏈表保存在shared.pending上,計算state的時候會剪開這個環狀鏈表,並且鏈接在lastBaseUpdate后
    • effects:calback不為null的update

    從fiber節點向上遍歷到rootFiber

    ​ 在markUpdateLaneFromFiberToRoot函數中會從觸發更新的節點開始向上遍歷到rootFiber,遍歷的過程會處理節點的優先級(第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) {//從觸發更新的節點開始向上遍歷到rootFiber
          parent.childLanes = mergeLanes(parent.childLanes, lane);//合並childLanes優先級
          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;
        }
      }
    

調度

​ 在ensureRootIsScheduled中,scheduleCallback會以一個優先級調度render階段的開始函數performSyncWorkOnRoot或者performConcurrentWorkOnRoot

if (newCallbackPriority === SyncLanePriority) {
  // 任務已經過期,需要同步執行render階段
  newCallbackNode = scheduleSyncCallback(
    performSyncWorkOnRoot.bind(null, root)
  );
} else {
  // 根據任務優先級異步執行render階段
  var schedulerPriorityLevel = lanePriorityToSchedulerPriority(
    newCallbackPriority
  );
  newCallbackNode = scheduleCallback(
    schedulerPriorityLevel,
    performConcurrentWorkOnRoot.bind(null, root)
  );
}

狀態更新

​ classComponent狀態計算發生在processUpdateQueue函數中,涉及很多鏈表操作,看圖更加直白

  • 初始時fiber.updateQueue單鏈表上有firstBaseUpdate(update1)和lastBaseUpdate(update2),以next連接

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

  • 計算state時,先將fiber.updateQueue.shared環狀鏈表‘剪開’,形成單鏈表,連接在fiber.updateQueue后面形成baseUpdate

  • 然后遍歷按這條鏈表,根據baseState計算出memoizedState

    _19

帶優先級的狀態更新

​ 類似git提交,這里的c3意味着高優先級的任務,比如用戶出發的事件,數據請求,同步執行的代碼等。

  • 通過ReactDOM.render創建的應用沒有優先級的概念,類比git提交,相當於先commit,然后提交c3

    _20

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

    ​ 優先級是由Scheduler來調度的,這里我們只關心狀態計算時的優先級排序,也就是在函數processUpdateQueue中發生的計算,例如初始時有c1-c4四個update,其中c1和c3為高優先級

    • 在第一次render的時候,低優先級的update會跳過,所以只有c1和c3加入狀態的計算

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

      在在concurrent模式下,componentWillMount可能會執行多次,變現和之前的版本不一致

      注意,fiber.updateQueue.shared會同時存在於workInprogress Fiber和current Fiber,目的是為了防止高優先級打斷正在進行的計算而導致狀態丟失,這段代碼也是發生在processUpdateQueue中

_21


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM