useState原理解析


一、初始化

構建dispatcher函數和初始值

二、更新時

  1. 調用dispatcher函數,按序插入update(其實就是一個action)

  2. 收集update,調度一次React的更新

  3. 在更新的過程中將ReactCurrentDispatcher.current指向負責更新的Dispatcher

  4. 執行到函數組件App()時,useState會被重新執行,在resolve dispatcher的階段拿到了負責更新的dispatcher。

  5. useState會拿到Hook對象,Hook.query中存儲了更新隊列,依次進行更新后,即可拿到最新的state

  6. 函數組件App()執行后返回的nextChild中的count值已經是最新的了。FiberNode中的memorizedState也被設置為最新的state

  7. Fiber渲染出真實DOM。更新結束

三、 了解useState

useState的引入

// React.js
import {
useCallback,
useContext,
useEffect,
useImperativeHandle,
useDebugValue,
useLayoutEffect,
useMemo,
useReducer,
useRef,
useState,
} from './ReactHooks';

所有的Hooks在React.js中被引入,掛載在React對象中

useState的實現

// ReactHooks.js
export function useState<S>(initialState: (() => S) | S) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

重點都在這個dispatcher上,dispatcher通過resolveDispatcher()來獲取,這個函數同樣也很簡單,只是將ReactCurrentDispatcher.current的值賦給了dispatcher。

// ReactHooks.js
function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;
  return dispatcher;
}

ReactCurrentDispatcher.current.useStateuseState能夠觸發更新的關鍵原因,這個方法的實現並不在react包內。

四. 核心步驟分析

ReactFiberHooks.js包含着各種關於Hooks邏輯的處理

Hook對象的結構如下:

// ReactFiberHooks.js
export type Hook = {
  memoizedState: any, 
 
  baseState: any,    
  baseUpdate: Update<any, any> | null,  
  queue: UpdateQueue<any, any> | null,  
 
  next: Hook | null, 
};

在類組件中state是一整個對象,可以和memoizedState一一對應。但是在Hooks中,React並不知道我們調用了幾次useState,所以React通過將一個Hook對象掛載在memorizedStated上來保存函數組件的state

重點關注memoizedStatenext

  • memoizedState是用來記錄當前useState應該返回的結果的

  • query:緩存隊列,存儲多次更新行為

  • next:指向下一次useState對應的Hook對象。

renderWithHooks

renderWithHooks的運行過程如下:

// ReactFiberHooks.js
export function renderWithHooks(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  props: any,
  refOrContext: any,
  nextRenderExpirationTime: ExpirationTime,
): any {
  renderExpirationTime = nextRenderExpirationTime;
  currentlyRenderingFiber = workInProgress;
 
  // 如果current的值為空,說明還沒有hook對象被掛載
  // 而根據hook對象結構可知,current.memoizedState指向下一個current
  nextCurrentHook = current !== null ? current.memoizedState : null;
 
  // 用nextCurrentHook的值來區分mount和update,設置不同的dispatcher
  ReactCurrentDispatcher.current =
      nextCurrentHook === null
      // 初始化時
        ? HooksDispatcherOnMount
          // 更新時
        : HooksDispatcherOnUpdate;
 
  // 此時已經有了新的dispatcher,在調用Component時就可以拿到新的對象
  let children = Component(props, refOrContext);
 
  // 重置
  ReactCurrentDispatcher.current = ContextOnlyDispatcher;
 
  const renderedWork: Fiber = (currentlyRenderingFiber: any);
 
  // 更新memoizedState和updateQueue
  renderedWork.memoizedState = firstWorkInProgressHook;
  renderedWork.updateQueue = (componentUpdateQueue: any);
 
   /** 省略與本文無關的部分代碼,便於理解 **/
}

初始化時

核心:創建一個新的hook,初始化state, 並綁定觸發器

 初始化階段ReactCurrentDispatcher.current 會指向HooksDispatcherOnMount 對象

// ReactFiberHooks.js
 
const HooksDispatcherOnMount: Dispatcher = {
/** 省略其它Hooks **/
  useState: mountState,
};
 
// 所以調用useState(0)返回的就是HooksDispatcherOnMount.useState(0),也就是mountState(0)
function mountState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
    // 訪問Hook鏈表的下一個節點,獲取到新的Hook對象
  const hook = mountWorkInProgressHook();
//如果入參是function則會調用,但是不提供參數
  if (typeof initialState === 'function') {
    initialState = initialState();
  }
// 進行state的初始化工作
  hook.memoizedState = hook.baseState = initialState;
// 進行queue的初始化工作
  const queue = (hook.queue = {
    last: null,
    dispatch: null,
    eagerReducer: basicStateReducer, // useState使用基礎reducer
    eagerState: (initialState: any),
  });
    // 返回觸發器
  const dispatch: Dispatch<BasicStateAction<S>,> 
    = (queue.dispatch = (dispatchAction.bind(
        null,
        //綁定當前fiber結點和queue
        ((currentlyRenderingFiber: any): Fiber),
        queue,
  ));
  // 返回初始state和觸發器
  return [hook.memoizedState, dispatch];
}
 
// 對於useState觸發的update action來說(假設useState里面都傳的變量),basicStateReducer就是直接返回action的值
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
  return typeof action === 'function' ? action(state) : action;
}

更新函數 dispatchAction

function dispatchAction<S, A>(
  fiber: Fiber,
  queue: UpdateQueue<S, A>,
  action: A,
) {
 
   /** 省略Fiber調度相關代碼 **/
 
  // 創建新的新的update, action就是我們setCount里面的值(count+1, count+2, count+3…)
    const update: Update<S, A> = {
      expirationTime,
      action,
      eagerReducer: null,
      eagerState: null,
      next: null,
    };
 
    // 重點:構建query
    // queue.last是最近的一次更新,然后last.next開始是每一次的action
    const last = queue.last;
    if (last === null) {
      // 只有一個update, 自己指自己-形成環
      update.next = update;
    } else {
      const first = last.next;
      if (first !== null) {
 
        update.next = first;
      }
      last.next = update;
    }
    queue.last = update;
 
    /** 省略特殊情況相關代碼 **/
 
    // 創建一個更新任務
    scheduleWork(fiber, expirationTime);
 
}

dispatchAction中維護了一份query的數據結構。

query是一個有環鏈表,規則:

  • query.last指向最近一次更新

  • last.next指向第一次更新

  • 后面就依次類推,最終倒數第二次更新指向last,形成一個環。

所以每次插入新update時,就需要將原來的first指向query.last.next。再將update指向query.next,最后將query.last指向update.

更新時

核心:獲取該Hook對象中的 queue,內部存有本次更新的一系列數據,進行更新

更新階段 ReactCurrentDispatcher.current 會指向HooksDispatcherOnUpdate對象

// ReactFiberHooks.js
 
// 所以調用useState(0)返回的就是HooksDispatcherOnUpdate.useState(0),也就是updateReducer(basicStateReducer, 0)
 
const HooksDispatcherOnUpdate: Dispatcher = {
  /** 省略其它Hooks **/
   useState: updateState,
}
 
function updateState(initialState) {
  return updateReducer(basicStateReducer, initialState);
}
 
// 可以看到updateReducer的過程與傳的initalState已經無關了,所以初始值只在第一次被使用
 
// 為了方便閱讀,刪去了一些無關代碼
// 查看完整代碼:https://github.com/facebook/react/blob/487f4bf2ee7c86176637544c5473328f96ca0ba2/packages/react-reconciler/src/ReactFiberHooks.js#L606
function updateReducer(reducer, initialArg, init) {
// 獲取初始化時的 hook
  const hook = updateWorkInProgressHook();
  const queue = hook.queue;
 
  // 開始渲染更新
  if (numberOfReRenders > 0) {
    const dispatch = queue.dispatch;
    if (renderPhaseUpdates !== null) {
      // 獲取Hook對象上的 queue,內部存有本次更新的一系列數據
      const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
      if (firstRenderPhaseUpdate !== undefined) {
        renderPhaseUpdates.delete(queue);
        let newState = hook.memoizedState;
        let update = firstRenderPhaseUpdate;
        // 獲取更新后的state
        do {
          const action = update.action;
          // 此時的reducer是basicStateReducer,直接返回action的值
          newState = reducer(newState, action);
          update = update.next;
        } while (update !== null);
        // 對 更新hook.memoized 
        hook.memoizedState = newState;
        // 返回新的 state,及更新 hook 的 dispatch 方法
        return [newState, dispatch];
      }
    }
  }
 
// 對於useState觸發的update action來說(假設useState里面都傳的變量),basicStateReducer就是直接返回action的值
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
  return typeof action === 'function' ? action(state) : action;
}

總結

單個hooks的更新行為全都掛在Hooks.queue下,所以能夠管理好queue的核心就在於

  • 初始化queue - mountState

  • 維護queue - dispatchAction

  • 更新queue - updateReducer

結合示例代碼:

  • 當我們第一次調用[count, setCount] = useState(0)時,創建一個queue

  • 每一次調用setCount(x),就dispach一個內容為x的action(action的表現為:將count設為x),action存儲在queue中,以前面講述的有環鏈表規則來維護

  • 這些action最終在updateReducer中被調用,更新到memorizedState上,使我們能夠獲取到最新的state值。


文章就分享到這,歡迎關注“前端大神之路

 


免責聲明!

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



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