【react hooks】--初渲染和更新階段


hook組件初渲染

hooks組件在初次渲染時,

  1. 解析組件類型,判斷是Function還是class類型,然后執行對應類型的處理方法
  2. 判斷到當前是Function類型組件后,首先在當前組件,也就是fiberNode上進行hook的創建和掛載,將所有的hook api都掛載到全局變量dispatcher
  3. 順序執行當前組件,每遇到一個hook api都通過next將它連接到當前fiberNodehook鏈表上

hooks api 掛載

在初始渲染時,currentDispatcher為空,就會先將所有hook api掛載到當前fiberNodedispatcher上。也就是將HooksDispatcherOnMountInDEV賦值給dispatcher

  {
    //  首次執行currentDispatcher = null,所以進入else分支;在更新階段會進入if分支
    if (currentDispatcher !== null) {
      currentDispatcher = HooksDispatcherOnUpdateInDEV;
    } else {
      currentDispatcher = HooksDispatcherOnMountInDEV;
    }
  }

而在HooksDispatcherOnMountInDEV中,包含各種原生hook,內部調用mountXXX方法。

HooksDispatcherOnMountInDEV = {
    useCallback: function (callback, deps) {
      return mountCallback(callback, deps);
    },
    useEffect: function (create, deps) {
      return mountEffect(create, deps);
    },
    useMemo: function (create, deps) {
        return mountMemo(create, deps);
    },
    useState: function (initialState) {
        return mountState(initialState);
    }
  }

useState -- mountState

  1. 首先創建一個hook節點,其中的memoizedState是最終返回的初始值;queue是更新隊列,當我們多次更新某一狀態時需要通過queue隊列存取和遍歷;next用來連接下一個hook;
  2. 將當前的hook連接到當前的fiberNodehook鏈表上
  3. 綁定狀態更新方法dispatchAction,並返回[state, dispatchAction]
function mountState(initailState){
    var hook = {
        moemoizedState: null,
        queue: {
            pending: null,
            dispatch: null,
        },
        next: null
    }
    ...
    var dispatch = queue.dispatch = dispatchAction.bind(null, currentFiber,queue);
    return [hook.memoizedState,dispatch];
}

useEffect -- mountEffectImpl

  1. mountEffect中,首先將當前的hook掛載到當前fiberNodehook鏈表上

  2. 由於useEffect是異步執行的,會產生專屬於useEffecthook。此時會將產生的useEffect產生的hook存入componentUpdateQueue更新隊列中。在某一次頁面渲染結束后會去遍歷這個更新隊列,執行傳入的effect hook

function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps) {
    // 創建並獲取當前hook節點信息
    var hook = mountWorkInProgressHook();

    hook.memoizedState = pushEffect(HasEffect | hookEffectTag, create, undefined, nextDeps);
  }
function mountWorkInProgressHook() {
    // 將當前hook連接到我們的hook鏈表中
    var hook = {
      memoizedState: null,
      queue: null,
      next: null
    };

    if (workInProgressHook === null) {
      currentlyRenderingFiber$1.memoizedState = workInProgressHook = hook;
    } else {
      workInProgressHook = workInProgressHook.next = hook;
    }

    return workInProgressHook;
  }
function pushEffect(tag, create, destroy, deps) {
    var effect = {
      tag: tag,  // 更新標識
      create: create, // 傳入的回調,也就是我們開發時的第一個參數
      destroy: destroy, // return 的函數,組件銷毀時執行的函數
      deps: deps, // 依賴項數組
      next: null
    };
    var componentUpdateQueue = currentlyRenderingFiber$1.updateQueue;
    
    // 這里做的就是把每個useEffect hook單獨以鏈式結構存到了componentUpdateQueue這個全局變量中
    if (componentUpdateQueue === null) {
      componentUpdateQueue = createFunctionComponentUpdateQueue();
      componentUpdateQueue.lastEffect = effect.next = effect;
    } else {
      var lastEffect = componentUpdateQueue.lastEffect;

      var firstEffect = lastEffect.next;
      lastEffect.next = effect;
      effect.next = firstEffect;
      componentUpdateQueue.lastEffect = effect;
    }
    
    return effect;
  }

初始化渲染至此結束,此時fiberNodehook鏈式結構是

currentFiber: {
	...
	memoizedState: {
		memoizedState: xxx,
		...
		next: {
			memoizedState: xxx,
			...
			next: {
				memoizedState: xxx,
				...
				next:hook4
			}
		}
	}
}

hook組件更新階段

在組件更新時,於初始化類似,將更新的對應hook進行掛載,根據鏈表依次執行hook。在此時,需要執行狀態更新和useEffect更新,最后更新完成。

useState

在初始化階段有提到,mountState階段會綁定dispatchAction並作為參數返回,其實也就是使用useState時返回的setXXX

而在dispatchAction中實際上,做了兩件事

  1. 創建update節點,並連接到useStatequeue后面,這樣每次調用dispatchAction都會在后面連接一個update節點,從而生成一個更新隊列。
  2. 然后根據更新任務的優先級排列任務,最后遍歷整個fiber樹執行更新操作。

執行setXXX后,發生了什么?

在初始化階段講到的,根據是否存在dispatcher,賦值。

  {
    //  更新階段dispatcher已存在,執行else部分
    if (currentDispatcher !== null) {
      currentDispatcher = HooksDispatcherOnUpdateInDEV;
    } else {
      currentDispatcher = HooksDispatcherOnMountInDEV;
    }
  }

而在HooksDispatcherOnMountInDEV中各種hooks對應的是update方法,useState對應的是updateState方法。

function updateState(initialState){    return updateReducer(basicStateReducer);}

可以看到,updateState實際上是返回了一個reducer

  1. updateReducer中,獲取到hook的更新隊列,比如執行了三次setCount,則隊列中就會存在三項。

  2. 拿到更新隊列后,會對其進行循環遍歷,計算賦值,最終會將最新的state值復制到hook的memoizedState上並返回。

function updateReducer(reducer, initialArg, init) {  // 獲取到當前hook,其實也就是直接.next就可以  var hook = updateWorkInProgressHook();  var queue = hook.queue;  // 取到待更新的隊列  var pendingQueue = queue.pending;    // 如果待更新隊列不為空,那么遍歷處理  if (pendingQueue !== null) {    var first = pendingQueue.next;    var newState = null;    var update = first;    queue.pending = null;        // 循環遍歷,是更新階段的核心和關鍵,    do {      var action = update.action;      newState = reducer(newState, action);      update = update.next;    } while (update !== null && update !== first);    // 最新的狀態值賦值給memoizedState    hook.memoizedState = newState;  }  // 將狀態值和更新方法返回,就和初次渲染一樣的流程  var dispatch = queue.dispatch;  return [hook.memoizedState, dispatch];}

如何形成更新隊列?

而在updateReducer之前,會執行dispatchAction,將每個update加入到更新隊列中。

// dispatchAction核心代碼var pending = queue.pending;// 這里是鏈表創建和連接的核心if (pending === null) {  update.next = update;} else {  update.next = pending.next;  pending.next = update;}queue.pending = update;

每次dispatch時,當目前更新隊列為空時,將當前update加入,next指向自己

當不為空時,將當前update放到更新隊列最后一項的next上。

需要注意的是,更新隊列中的first指向第一個執行的updatequeue.pending指向最后一個。

所以在更新隊列循環執行時,結束循環的條件為while (update !== null && update !== first);也就是當前update為更新隊列中的第一個也就是唯一一個update

useEffect

useState相似,在更新過程中會執行updateEffect,在其中執行updateEffectImpl

updateEffectImpl中,無論依賴項是否更改,都會調用pushEffect方法。

function updateEffectImpl(fiberEffectTag, hookEffectTag, create, deps) {  // 獲取到當前hook  var hook = updateWorkInProgressHook();  // 比較依賴項是否發生了變化  if (areHookInputsEqual(nextDeps, prevDeps)) {    // 如果相同則不對當前hook的屬性進行更新    pushEffect(hookEffectTag, create, destroy, nextDeps);    return;  }  // 如果依賴項發生了變化,更新當前hook的memoizedState,這里的賦值只是做一個記錄,並沒有實際意義  currentlyRenderingFiber$1.effectTag |= fiberEffectTag;  hook.memoizedState = pushEffect(HasEffect | hookEffectTag, create, destroy, nextDeps);}

而在pushEffect方法中,

  1. 創建一個effect對象,並返回
  2. 創建/更新componentUpdateQueue隊列,其中存儲了useEffect產生的回調,componentUpdateQueue隊列不存在的話會進行創建,如果存在,會和mountState階段一樣創建一個effect的循環鏈表。effect對象中的tag就是用來判斷這個useEffect回調是否需要被執行。

一些可以被解釋了的問題

hook必須按照固定順序調用,不能在條件判斷中調用

由於每一個hook都是通過next指針在鏈表中按順序連接的,如果在某個條件判斷的情況下,某個hook不存在,就會導致整個hook鏈表中斷,沒法繼續正常遍歷hook鏈表,產生問題。


免責聲明!

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



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