9.hooks源碼(想知道Function Component是怎樣保存狀態(tài)的嘛)

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

9.hooks源碼(想知道Function Component是怎樣保存狀態(tài)的嘛)

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

視頻課程的目的是為了快速掌握react源碼運(yùn)行的過程和react中的scheduler叶撒、reconciler补疑、renderer、fiber等阿浓,并且詳細(xì)debug源碼和分析丙躏,過程更清晰墨叛。

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

demos:demo

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

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

hook調(diào)用入口

在hook源碼中hook存在于Dispatcher中骏全,Dispatcher就是一個對象与斤,不同hook 調(diào)用的函數(shù)不一樣肪康,全局變量ReactCurrentDispatcher.current會根據(jù)是mount還是update賦值為HooksDispatcherOnMount或HooksDispatcherOnUpdate
ReactCurrentDispatcher.current = 
  current === null || current.memoizedState === null//mount or update
  ? HooksDispatcherOnMount
    : HooksDispatcherOnUpdate;  
const HooksDispatcherOnMount: Dispatcher = {//mount時
  useCallback: mountCallback,
  useContext: readContext,
  useEffect: mountEffect,
  useImperativeHandle: mountImperativeHandle,
  useLayoutEffect: mountLayoutEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState,
  //...
};

const HooksDispatcherOnUpdate: Dispatcher = {//update時
  useCallback: updateCallback,
  useContext: readContext,
  useEffect: updateEffect,
  useImperativeHandle: updateImperativeHandle,
  useLayoutEffect: updateLayoutEffect,
  useMemo: updateMemo,
  useReducer: updateReducer,
  useRef: updateRef,
  useState: updateState,
  //...
};

hook數(shù)據(jù)結(jié)構(gòu)

在FunctionComponent中,多個hook會形成hook鏈表撩穿,保存在Fiber的memoizedState的上磷支,而需要更新的Update保存在hook.queue.pending中
const hook: Hook = {
  memoizedState: null,//對于不同hook,有不同的值
  baseState: null,//初始state
  baseQueue: null,//初始queue隊列
  queue: null,//需要更新的update
  next: null,//下一個hook
};

下面來看下memoizedState對應(yīng)的值

  • useState:例如const [state, updateState] = useState(initialState)食寡,memoizedState等于state的值
  • useReducer:例如const [state, dispatch] = useReducer(reducer, {});雾狈,memoizedState等于state的值
  • useEffect:在mountEffect時會調(diào)用pushEffect創(chuàng)建effect鏈表,memoizedState就等于effect鏈表抵皱,effect鏈表也會掛載到fiber.updateQueue上善榛,每個effect上存在useEffect的第一個參數(shù)回調(diào)和第二個參數(shù)依賴數(shù)組,例如呻畸,useEffect(callback, [dep])移盆,effect就是{create:callback, dep:dep,...}
  • useRef:例如useRef(0),memoizedState就等于{current: 0}
  • useMemo:例如useMemo(callback, [dep])伤为,memoizedState等于[callback(), dep]
  • useCallback:例如useCallback(callback, [dep])咒循,memoizedState等于[callback, dep]useCallback保存callback函數(shù)绞愚,useMemo保存callback的執(zhí)行結(jié)果

useState&useReducer

之所以把useState和useReducer放在一起叙甸,是因為在源碼中useState就是有默認(rèn)reducer參數(shù)的useReducer。

  • useState&useReducer聲明

    resolveDispatcher函數(shù)會獲取當(dāng)前的Dispatcher

    function useState(initialState) {
      var dispatcher = resolveDispatcher();
      return dispatcher.useState(initialState);
    }
    function useReducer(reducer, initialArg, init) {
      var dispatcher = resolveDispatcher();
      return dispatcher.useReducer(reducer, initialArg, init);
    }
    
    
  • mount階段

    mount階段useState調(diào)用mountState位衩,useReducer調(diào)用mountReducer裆蒸,唯一區(qū)別就是它們創(chuàng)建的queue中l(wèi)astRenderedReducer不一樣,mount有初始值basicStateReducer糖驴,所以說useState就是有默認(rèn)reducer參數(shù)的useReducer僚祷。

    function mountState<S>(//
      initialState: (() => S) | S,
    ): [S, Dispatch<BasicStateAction<S>>] {
      const hook = mountWorkInProgressHook();//創(chuàng)建當(dāng)前hook
      if (typeof initialState === 'function') {
        initialState = initialState();
      }
      hook.memoizedState = hook.baseState = initialState;//hook.memoizedState賦值
      const queue = (hook.queue = {//賦值hook.queue
        pending: null,
        dispatch: null,
        lastRenderedReducer: basicStateReducer,//和mountReducer的區(qū)別
        lastRenderedState: (initialState: any),
      });
      const dispatch: Dispatch<//創(chuàng)建dispatch函數(shù)
        BasicStateAction<S>,
      > = (queue.dispatch = (dispatchAction.bind(
        null,
        currentlyRenderingFiber,
        quewque,
      ): any));
      return [hook.memoizedState, dispatch];//返回memoizedState和dispatch
    }
    
    function mountReducer<S, I, A>(
      reducer: (S, A) => S,
      initialArg: I,
      init?: I => S,
    ): [S, Dispatch<A>] {
      const hook = mountWorkInProgressHook();//創(chuàng)建當(dāng)前hook
      let initialState;
      if (init !== undefined) {
        initialState = init(initialArg);
      } else {
        initialState = ((initialArg: any): S);
      }
      hook.memoizedState = hook.baseState = initialState;//hook.memoizedState賦值
      const queue = (hook.queue = {//創(chuàng)建queue
        pending: null,
        dispatch: null,
        lastRenderedReducer: reducer,
        lastRenderedState: (initialState: any),
      });
      const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(//創(chuàng)建dispatch函數(shù)
        null,
        currentlyRenderingFiber,
        queue,
      ): any));
      return [hook.memoizedState, dispatch];//返回memoizedState和dispatch
    }
    
    
    function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
      return typeof action === 'function' ? action(state) : action;
    }
    
  • update階段

    update時會根據(jù)hook中的update計算新的state

    function updateReducer<S, I, A>(
      reducer: (S, A) => S,
      initialArg: I,
      init?: I => S,
    ): [S, Dispatch<A>] {
      const hook = updateWorkInProgressHook();//獲取hook
      const queue = hook.queue;
      queue.lastRenderedReducer = reducer;
    
      //...更新state和第5章的state計算邏輯基本一致
    
      const dispatch: Dispatch<A> = (queue.dispatch: any);
      return [hook.memoizedState, dispatch];
    }
    
    
  • 執(zhí)行階段

    useState執(zhí)行setState后會調(diào)用dispatchAction,dispatchAction做的事情就是講Update加入queue.pending中贮缕,然后開始調(diào)度

    function dispatchAction(fiber, queue, action) {
    
      var update = {//創(chuàng)建update
        eventTime: eventTime,
        lane: lane,
        suspenseConfig: suspenseConfig,
        action: action,
        eagerReducer: null,
        eagerState: null,
        next: null
      }; 
    
      //queue.pending中加入update
      
      var alternate = fiber.alternate;
    
      if (fiber === currentlyRenderingFiber$1 || alternate !== null && alternate === currentlyRenderingFiber$1) {
        //如果是render階段執(zhí)行的更新didScheduleRenderPhaseUpdate=true
    }
        didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
      } else {
        if (fiber.lanes === NoLanes && (alternate === null || alternate.lanes === NoLanes)) {
          //如果fiber不存在優(yōu)先級并且當(dāng)前alternate不存在或者沒有優(yōu)先級久妆,那就不需要更新了
          //優(yōu)化的步驟
        }
    
        scheduleUpdateOnFiber(fiber, lane, eventTime);
      }
    }
    
    

useEffect

  • 聲明

    獲取并返回useEffect函數(shù)

    export function useEffect(
      create: () => (() => void) | void,
      deps: Array<mixed> | void | null,
    ): void {
      const dispatcher = resolveDispatcher();
      return dispatcher.useEffect(create, deps);
    }
    
  • mount階段

    調(diào)用mountEffect,mountEffect調(diào)用mountEffectImpl跷睦,hook.memoizedState賦值為effect鏈表

    function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {
      const hook = mountWorkInProgressHook();//獲取hook
      const nextDeps = deps === undefined ? null : deps;//依賴
      currentlyRenderingFiber.flags |= fiberFlags;//增加flag
      hook.memoizedState = pushEffect(//memoizedState=effects環(huán)狀鏈表
        HookHasEffect | hookFlags,
        create,
        undefined,
        nextDeps,
      );
    }
    
  • update階段

    淺比較依賴,如果依賴性變了pushEffect第一個參數(shù)傳HookHasEffect | hookFlags肋演,HookHasEffect表示useEffect依賴項改變了抑诸,需要在commit階段重新執(zhí)行

    function updateEffectImpl(fiberFlags, hookFlags, create, deps): void {
      const hook = updateWorkInProgressHook();
      const nextDeps = deps === undefined ? null : deps;
      let destroy = undefined;
    
      if (currentHook !== null) {
        const prevEffect = currentHook.memoizedState;
        destroy = prevEffect.destroy;//
        if (nextDeps !== null) {
          const prevDeps = prevEffect.deps;
          if (areHookInputsEqual(nextDeps, prevDeps)) {//比較deps
            //即使依賴相等也要將effect加入鏈表烂琴,以保證順序一致
            pushEffect(hookFlags, create, destroy, nextDeps);
            return;
          }
        }
      }
    
      currentlyRenderingFiber.flags |= fiberFlags;
    
      hook.memoizedState = pushEffect(
        //參數(shù)傳HookHasEffect | hookFlags,包含hookFlags的useEffect會在commit階段執(zhí)行這個effect
        HookHasEffect | hookFlags,
        create,
        destroy,
        nextDeps,
      );
    }
    
  • 執(zhí)行階段

    在第9章commit階段的commitLayoutEffects函數(shù)中會調(diào)用schedulePassiveEffects蜕乡,將useEffect的銷毀和回調(diào)函數(shù)push到pendingPassiveHookEffectsUnmount和pendingPassiveHookEffectsMount中奸绷,然后在mutation之后調(diào)用flushPassiveEffects依次執(zhí)行上次render的銷毀函數(shù)回調(diào)和本次render 的回調(diào)函數(shù)

    const unmountEffects = pendingPassiveHookEffectsUnmount;
    pendingPassiveHookEffectsUnmount = [];
    for (let i = 0; i < unmountEffects.length; i += 2) {
      const effect = ((unmountEffects[i]: any): HookEffect);
      const fiber = ((unmountEffects[i + 1]: any): Fiber);
      const destroy = effect.destroy;
      effect.destroy = undefined;
    
      if (typeof destroy === 'function') {
        try {
          destroy();//銷毀函數(shù)執(zhí)行
        } catch (error) {
          captureCommitPhaseError(fiber, error);
        }
      }
    }
    
    const mountEffects = pendingPassiveHookEffectsMount;
    pendingPassiveHookEffectsMount = [];
    for (let i = 0; i < mountEffects.length; i += 2) {
      const effect = ((mountEffects[i]: any): HookEffect);
      const fiber = ((mountEffects[i + 1]: any): Fiber);
      
      try {
        const create = effect.create;//本次render的創(chuàng)建函數(shù)
       effect.destroy = create();
      } catch (error) {
        captureCommitPhaseError(fiber, error);
      }
    }
    
    

useRef

sring類型的ref已經(jīng)不在推薦使用,F(xiàn)orwardRef只是把ref通過傳參傳下去层玲,createRef也是{current: any這種結(jié)構(gòu)号醉,所以我們只討論function或者{current: any}的useRef
//createRef返回{current: any}
export function createRef(): RefObject {
  const refObject = {
    current: null,
  };
  return refObject;
}
//ForwardRef第二個參數(shù)是ref對象
let children = Component(props, secondArg);
  • 聲明階段

    和其他hook一樣

    export function useRef<T>(initialValue: T): {|current: T|} {
      const dispatcher = resolveDispatcher();
      return dispatcher.useRef(initialValue);
    }
    
  • mount階段

    mount時會調(diào)用mountRef,創(chuàng)建hook和ref對象辛块。

    function mountRef<T>(initialValue: T): {|current: T|} {
      const hook = mountWorkInProgressHook();//獲取useRef
      const ref = {current: initialValue};//ref初始化
      hook.memoizedState = ref;
      return ref;
    }
    

    render階段:將帶有ref屬性的Fiber標(biāo)記上Ref Tag畔派,在一步發(fā)生在beginWork和completeWork函數(shù)中的markRef

    export const Ref = /*                          */ 0b0000000010000000;
    
    //beginWork中
    function markRef(current: Fiber | null, workInProgress: Fiber) {
      const ref = workInProgress.ref;
      if (
        (current === null && ref !== null) ||
        (current !== null && current.ref !== ref)
      ) {
        workInProgress.effectTag |= Ref;
      }
    }
    //completeWork中
    function markRef(workInProgress: Fiber) {
      workInProgress.effectTag |= Ref;
    }
    

    commit階段:

      會在commitMutationEffects函數(shù)中判斷ref是否改變,如果改變了會先執(zhí)行commitDetachRef先刪除之前的ref润绵,然后在commitLayoutEffect中會執(zhí)行commitAttachRef賦值ref线椰。
    
    function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
      while (nextEffect !== null) {
        const effectTag = nextEffect.effectTag;
        // ...
        
        if (effectTag & Ref) {
          const current = nextEffect.alternate;
          if (current !== null) {
            commitDetachRef(current);//移除ref
          }
        }
      }
    
    function commitDetachRef(current: Fiber) {
      const currentRef = current.ref;
      if (currentRef !== null) {
        if (typeof currentRef === 'function') {
          currentRef(null);//類型是function,則調(diào)用
        } else {
          currentRef.current = null;//否則賦值{current: null}
        }
      }
    }
    
    
    function commitAttachRef(finishedWork: Fiber) {
      const ref = finishedWork.ref;
      if (ref !== null) {
        const instance = finishedWork.stateNode;//獲取ref的實例
        let instanceToUse;
        switch (finishedWork.tag) {
          case HostComponent:
            instanceToUse = getPublicInstance(instance);
            break;
          default:
            instanceToUse = instance;
        }
    
        if (typeof ref === 'function') {//ref賦值
          ref(instanceToUse);
        } else {
          ref.current = instanceToUse;
        }
      }
    }
    
    
  • update階段

    update時調(diào)用updateRef獲取獲取當(dāng)前useRef尘盼,然后返回hook鏈表

    function updateRef<T>(initialValue: T): {|current: T|} {
      const hook = updateWorkInProgressHook();//獲取當(dāng)前useRef
      return hook.memoizedState;//返回hook鏈表
    }
    

useMemo&useCallback

  • 聲明階段

    和其他hook 一樣

  • mount階段

    mount階段useMemo和useCallback唯一區(qū)別是在memoizedState中存貯callback還是callback計算出來的函數(shù)

    function mountMemo<T>(
      nextCreate: () => T,
      deps: Array<mixed> | void | null,
    ): T {
      const hook = mountWorkInProgressHook();//創(chuàng)建hook
      const nextDeps = deps === undefined ? null : deps;
      const nextValue = nextCreate();//計算value
      hook.memoizedState = [nextValue, nextDeps];//把value和依賴保存在memoizedState中
      return nextValue;
    }
    
    function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
      const hook = mountWorkInProgressHook();//創(chuàng)建hook
      const nextDeps = deps === undefined ? null : deps;
      hook.memoizedState = [callback, nextDeps];//把callback和依賴保存在memoizedState中
      return callback;
    }
    
  • update階段

    update時也一樣憨愉,唯一區(qū)別就是直接用回調(diào)函數(shù)還是執(zhí)行回調(diào)后返回的value作為[?, nextDeps]賦值給memoizedState

function updateMemo<T>(
  nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {
  const hook = updateWorkInProgressHook();//獲取hook
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;

  if (prevState !== null) {
    if (nextDeps !== null) {
      const prevDeps: Array<mixed> | null = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {//淺比較依賴
        return prevState[0];//沒變 返回之前的狀態(tài)
      }
    }
  }
  const nextValue = nextCreate();//有變化重新調(diào)用callback
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
  const hook = updateWorkInProgressHook();//獲取hook
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;

  if (prevState !== null) {
    if (nextDeps !== null) {
      const prevDeps: Array<mixed> | null = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {//淺比較依賴
        return prevState[0];//沒變 返回之前的狀態(tài)
      }
    }
  }

  hook.memoizedState = [callback, nextDeps];//變了重新將[callback, nextDeps]賦值給memoizedState
  return callback;
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市卿捎,隨后出現(xiàn)的幾起案子配紫,更是在濱河造成了極大的恐慌午阵,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件括细,死亡現(xiàn)場離奇詭異戚啥,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)猫十,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門览濒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人拖云,你說我怎么就攤上這事≈嫦睿” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵汇荐,是天一觀的道長。 經(jīng)常有香客問我旬蟋,道長革娄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任匆浙,我火速辦了婚禮架忌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好埋嵌,可當(dāng)我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布雹嗦。 她就那樣靜靜地躺著了罪,像睡著了一般聪全。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上难礼,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天,我揣著相機(jī)與錄音蛾茉,去河邊找鬼。 笑死悦屏,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的散劫。 我是一名探鬼主播幕帆,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼失乾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了碱茁?” 一聲冷哼從身側(cè)響起仿贬,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蜓氨,沒想到半個月后队伟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡港令,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年顷霹,在試婚紗的時候發(fā)現(xiàn)自己被綠了击吱。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡绅喉,死狀恐怖叫乌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情憨奸,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布那婉,位于F島的核電站,受9級特大地震影響党瓮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜寞奸,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望枪萄。 院中可真熱鬧,春花似錦聚凹、人聲如沸齐帚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽饥伊。三九已至,卻和暖如春琅豆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蚪拦。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工冻押, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人括袒。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓稿茉,卻偏偏與公主長得像锹锰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子恃慧,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,490評論 2 348

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

  • 使用React Hooks有什么優(yōu)勢彪薛? 什么是hookshook 是一些可以讓你在函數(shù)組件里面鉤入react st...
    Lyan_2ab3閱讀 350評論 0 1
  • React Hooks 原理[https://github.com/brickspert/blog/issues/...
    Yong_bcf4閱讀 761評論 0 1
  • 原文地址React Hooks實例教學(xué) 目前怠蹂,Hooks 應(yīng)該是 React 中最火的概念了,在閱讀這篇文章之前褥蚯,...
    DC_er閱讀 15,775評論 4 14
  • 今天感恩節(jié)哎赞庶,感謝一直在我身邊的親朋好友澳骤。感恩相遇!感恩不離不棄为肮。 中午開了第一次的黨會,身份的轉(zhuǎn)變要...
    迷月閃星情閱讀 10,559評論 0 11
  • 彩排完茅特,天已黑
    劉凱書法閱讀 4,197評論 1 3