從源碼加深理解 Hook 使用規(guī)則

前言

在官網(wǎng)中的Hook 規(guī)則里,講到了使用 Hook 需要遵循的兩條規(guī)則:

  1. 不要在循環(huán)毫痕,條件或嵌套函數(shù)中調(diào)用 Hook典格。
  2. 只在 React 函數(shù)中調(diào)用 Hook试疙。

接下來從源碼看看到底為什么棋凳。

ReactHooks 的源碼里可以看到 useState, useEffect笙隙,useRef刃唤,useMemo隔心,useCallback...... 所有的 Hooks 里的代碼都大同小異,都是調(diào)用 resolveDispatcher 尚胞,下面以 useState 為例硬霍。

為何不要在循環(huán),條件或嵌套函數(shù)中調(diào)用 Hook笼裳?

export function useState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}
function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;
  return ((dispatcher: any): Dispatcher);
}

可以看到 hooks 都是從 ReactCurrentDispatcher 上的 current 來的唯卖,初始化為null。(Dispatcher 里聲明的躬柬,正好是一些 hooks 的類型)

const ReactCurrentDispatcher = {
  /**
   * @internal
   * @type {ReactComponent}
   */
  current: (null: null | Dispatcher),
};
問題:ReactCurrentDispatcher 上的 current 是在哪里被賦值的呢耐床?

在 github 上搜索 ReactCurrentDispatcher 可以發(fā)現(xiàn)是在 ReactFiberHooks.new.js 文件里的 renderWithHooks 賦值的。(從名字上就可以感覺到這個(gè)和 fiber 架構(gòu)和 hooks 有關(guān)了)

export function renderWithHooks<Props, SecondArg>(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: (p: Props, arg: SecondArg) => any,
  props: Props,
  secondArg: SecondArg,
  nextRenderLanes: Lanes,
): any {
    //......
    ReactCurrentDispatcher.current =
      current === null || current.memoizedState === null
        ? HooksDispatcherOnMount
        : HooksDispatcherOnUpdate;
    //......
  }

可以看到初始化和更新的 hooks 是兩套 api楔脯。
我們先只看mount 階段的 HooksDispatcherOnMount

const HooksDispatcherOnMount: Dispatcher = {
  readContext,

  useCallback: mountCallback,
  useContext: readContext,
  useEffect: mountEffect,
  useImperativeHandle: mountImperativeHandle,
  useLayoutEffect: mountLayoutEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState,
  useDebugValue: mountDebugValue,
  useDeferredValue: mountDeferredValue,
  useTransition: mountTransition,
  useMutableSource: mountMutableSource,
  useOpaqueIdentifier: mountOpaqueIdentifier,

  unstable_isNewReconciler: enableNewReconciler,
};

useState 執(zhí)行的自然就是 mountState

function mountState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  const hook = mountWorkInProgressHook();
  if (typeof initialState === 'function') {
    initialState = initialState();
  }
  hook.memoizedState = hook.baseState = initialState;
  //......
  return [hook.memoizedState, dispatch];
}
function mountWorkInProgressHook(): Hook {
  const hook: Hook = {
    memoizedState: null,

    baseState: null,
    baseQueue: null,
    queue: null,

    next: null,
  };

  if (workInProgressHook === null) {
    // This is the first hook in the list
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    // Append to the end of the list
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

可以看到 mountWorkInProgressHook 里的代碼胯甩,每次生成一個(gè) hook 對(duì)象(里面保存了當(dāng)前 hook 信息)昧廷,然后通過 next 指向下一個(gè) hook堪嫂,以鏈表形式串聯(lián)起來。

總結(jié):hooks 的關(guān)系是通過 next 指向進(jìn)行關(guān)聯(lián)的木柬,所以如果寫在循環(huán)或條件語句中的話皆串,next 的順序就亂了,沒有辦法按照正確的順序執(zhí)行了眉枕。

為何只能在 React 函數(shù)中調(diào)用 Hook恶复?

從上面我們已經(jīng)可以知道,hooks 是在 renderWithHooks 內(nèi)賦值的速挑。

問題:renderWithHooks 又是在哪里被 import 使用了呢谤牡?

我們再來搜一下 renderWithHooks,可以看到是在 ReactFiberBeginWork.new.js 文件內(nèi)被使用的 姥宝。(從文件的命名上翅萤,我們可以知道,這個(gè)是 fiber 開始執(zhí)行的 js 文件)

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
 //......
  switch (workInProgress.tag) {
    //......
    case FunctionComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateFunctionComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes,
      );
    }
    case ClassComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateClassComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes,
      );
    }
    //......
  }
 //......

從這一段代碼里我們可以知道腊满,函數(shù)組件和 class 組件 調(diào)用的是不同的方法套么,而 updateFunctionComponent 里調(diào)用了 renderWithHooks 函數(shù),updateClassComponent 沒有調(diào)用碳蛋。所以關(guān)于為什么的答案已經(jīng)出來了胚泌。

總結(jié)一下:hooks 需要從 ReactCurrentDispatcher.current 上取得,而其賦值是在執(zhí)行 renderWithHooks 時(shí)賦值的肃弟。執(zhí)行函數(shù)組件的 updateFunctionComponent 內(nèi)有調(diào)用 renderWithHooks 函數(shù)玷室,執(zhí)行 class 組件的 updateClassComponent 內(nèi)沒有調(diào)用其方法,所以使用 hooks 時(shí)必須寫在函數(shù)組件內(nèi)愕乎。

最后

希望這是加深對(duì) hook 原理了解的第一步阵苇。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市感论,隨后出現(xiàn)的幾起案子绅项,更是在濱河造成了極大的恐慌,老刑警劉巖比肄,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件快耿,死亡現(xiàn)場離奇詭異,居然都是意外死亡芳绩,警方通過查閱死者的電腦和手機(jī)掀亥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來妥色,“玉大人搪花,你說我怎么就攤上這事。” “怎么了撮竿?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵吮便,是天一觀的道長。 經(jīng)常有香客問我幢踏,道長髓需,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任房蝉,我火速辦了婚禮僚匆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘搭幻。我一直安慰自己咧擂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布粗卜。 她就那樣靜靜地躺著屋确,像睡著了一般。 火紅的嫁衣襯著肌膚如雪续扔。 梳的紋絲不亂的頭發(fā)上攻臀,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音纱昧,去河邊找鬼刨啸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛识脆,可吹牛的內(nèi)容都是我干的设联。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼灼捂,長吁一口氣:“原來是場噩夢啊……” “哼离例!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起悉稠,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤宫蛆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后的猛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體耀盗,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年卦尊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了叛拷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡岂却,死狀恐怖忿薇,靈堂內(nèi)的尸體忽然破棺而出裙椭,到底是詐尸還是另有隱情,我是刑警寧澤煌恢,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布骇陈,位于F島的核電站,受9級(jí)特大地震影響瑰抵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜器联,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一二汛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧拨拓,春花似錦肴颊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至醋界,卻和暖如春竟宋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背形纺。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來泰國打工丘侠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人逐样。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓蜗字,卻偏偏與公主長得像,于是被迫代替她去往敵國和親脂新。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挪捕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

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

  • useState是一個(gè)Hook函數(shù),讓你在函數(shù)組件中擁有state變量争便。它接收一個(gè)初始化的state级零,返回是一個(gè)數(shù)...
    _LG_閱讀 21,799評(píng)論 0 16
  • 關(guān)于 hooks 使用過程中的疑問: 為什么 useEffect 第二個(gè)參數(shù)是空數(shù)組,就相當(dāng)于 Component...
    Thomas趙騏閱讀 571評(píng)論 0 0
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月始花,有人笑有人哭妄讯,有人歡樂有人憂愁,有人驚喜有人失落酷宵,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,535評(píng)論 28 53
  • 人工智能是什么亥贸?什么是人工智能?人工智能是未來發(fā)展的必然趨勢嗎浇垦?以后人工智能技術(shù)真的能達(dá)到電影里機(jī)器人的智能水平嗎...
    ZLLZ閱讀 3,776評(píng)論 0 5
  • 首先介紹下自己的背景: 我11年左右入市到現(xiàn)在,也差不多有4年時(shí)間朴摊,看過一些關(guān)于股票投資的書籍默垄,對(duì)于巴菲特等股神的...
    瞎投資閱讀 5,724評(píng)論 3 8