新特性 Hook 簡述

pardon110發表於2019-08-06

React 16.8+ 新增一批,以use字首開頭的函式,讓函式元件也可以使用 state 以及其他的 React 特性。 本文簡述這批鉤子函式(hook)一些使用技巧及應用場景, 並試圖探究讓 hook 節點結構佇列和它的狀態可以在外部定位原理

Hook概覽

  • useState 函式
    • useState返回一對值:當前狀態和一個用於更新它的函式
    • 可以在事件處理函式中或其他一些地方呼叫這個函式, 類似 class 元件的this.setState
      • 但它不會把新的 state 與舊的 state 進行合併
  • 宣告多個 state 變數, 使用陣列解構語法,可給 state 變數取不同的名字

State Hook

  • 在函式元件內鉤入一些 React State 及生命週期等特性的函式
  • Hook 來複用不同元件之間的狀態邏輯,通常只用在函式元件

Effect Hook

  • 在 React 元件中執行過資料獲取、訂閱或者手動修改過 DOM等操作,謂之"副作用"
  • useEffect 函式 給函式元件增加了操作副作用的能力
    • 預設情況下,React 會在每次渲染後呼叫副作用函式 —— 包括第一次渲染的時候
    • 呼叫 useEffect 時,就是在告訴 React 在完成對 DOM 的更改後執行你的“副作用”函式
    • 副作用函式還可以通過返回一個函式來指定如何“清除”副作用
      • 與 useState 一樣,可在元件中多次使用 useEffect

Hook 規則

  • Hook 就是 JavaScript 函式,有兩個額外規則
    • 只能在函式最外層呼叫 Hook。不要在迴圈、條件判斷或者子函式中呼叫
    • 只能在 React 的函式元件中呼叫 Hook(自定義的 Hook 中也可)
  • 官方提供 linter 外掛來自動執行這些規則

自定義Hook

  • 在元件之間重用一些狀態邏輯
    • 當前兩種主流方案:高階元件和 render props
    • 自定義 Hook 可以讓你在不增加元件的情況下達到同樣的目的
  • Hook 是一種複用狀態邏輯的方式,它不復用 state 本身
    • 自定義Hook類似於一種約定而不是功能
    • 若函式的名字以use開頭並呼叫其他Hook,則稱之為自定義Hook
    • useSomething 的命名約定可以讓 linter 外掛在使用 Hook 的程式碼中找到 bug
  • 應用場景
    • 表單處理,動畫,訂閱宣告,計時器

其他Hook

  • useContext 不使用元件巢狀就可以訂閱 React 的 Context
  • useReducer 通過 reducer 來管理元件本地的複雜 state

Hook系統關鍵

確保Hook 在React作用域內使用

新特性 Hook

Dispatcher

  • Dispatcher 是一個包含了 hook 函式的共享物件
    • 基於 ReactDOM 的渲染狀態,它將會被動態的分配或者清理,並且它將會確保使用者不能在 React 元件之外獲取到 hook
      • 具體做法 通過一個名為 enableHooks 的標誌來啟用/禁用 hook,當完成渲染工作後,React 會廢棄當前的 dispatcher 並禁止 hook
  • Dispatcher 在每次 hook 的呼叫中都會被函式 resolveDispatcher() 解析

Hook 佇列

在 React 後臺,hook 會被表示為節點,並以呼叫順序連線起來。 hook 並不是被簡單的建立然後丟棄,它們有一套獨有的機制,一個 hook 會有數個屬性

執行流程

  1. 在初次渲染的時候,它的初始狀態會被建立
  2. 它的狀態可以在執行時更新
  3. React 可以在後續渲染中記住 hook 的狀態
  4. React 能根據呼叫順序提供正確的狀態
  5. React 知道當前 hook 屬於哪個部分
  • React 狀態視角
    通常只把元件狀態看作一個簡單的物件,但當處理 hook 的時候,狀態需要被看作是一個佇列,每個節點都表示了物件的一個模組,其hook節點簡易結構如下

    {
    memoizedState: 'foo',
    next: {
      memoizedState: 'bar',
      next: {
        memoizedState: 'bar',
        next: null
      }
    }
    }

    hook 執行的關鍵程式碼在於 memoizedStatenext,其他(如baseState,baseUpdate,queue輔助性)的屬性會被useReducer() hook 使用,來快取傳送過的 action 以及基本的狀態。在每個函式元件呼叫前,一個名為 prepareHooks() 的函式將先被呼叫,在這個函式中,當前結構和 hook 佇列中的第一個 hook 節點將被儲存在全域性變數中。以便達到任何時候呼叫 hook 函式(useXXX()),都能知道當前執行的上下文

  • Hook 佇列的實現邏輯
    一旦更新完成,finishHook() 所在的函式將會被呼叫,在這個函式中,hook 佇列的第一個節點的引用將會被儲存在渲染了的結構 memoizedState 屬性中,從外部讀取某一元件狀態變遷

  • State Hook
    useState 這個 hook 在後臺使用了 useReducer,並且它將 useReducer 作為預定義的 reducer
    這意味著,useState 返回的結果實際上已經是 reducer 的狀態,同時也是 action dispatcher