從
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
作用域內使用
Dispatcher
Dispatcher
是一個包含了hook
函式的共享物件- 基於
ReactDOM
的渲染狀態,它將會被動態的分配或者清理,並且它將會確保使用者不能在React
元件之外獲取到hook
- 具體做法 通過一個名為
enableHooks
的標誌來啟用/禁用hook
,當完成渲染工作後,React
會廢棄當前的dispatcher
並禁止hook
- 具體做法 通過一個名為
- 基於
Dispatcher
在每次hook
的呼叫中都會被函式resolveDispatcher()
解析
Hook 佇列
在 React 後臺,hook 會被表示為節點,並以呼叫順序連線起來。 hook 並不是被簡單的建立然後丟棄,它們有一套獨有的機制,一個 hook 會有數個屬性
執行流程
- 在初次渲染的時候,它的初始狀態會被建立
- 它的狀態可以在執行時更新
- React 可以在後續渲染中記住 hook 的狀態
- React 能根據呼叫順序提供正確的狀態
- React 知道當前 hook 屬於哪個部分
-
React 狀態視角
通常只把元件狀態看作一個簡單的物件,但當處理 hook 的時候,狀態需要被看作是一個佇列,每個節點都表示了物件的一個模組,其hook節點簡易結構如下{ memoizedState: 'foo', next: { memoizedState: 'bar', next: { memoizedState: 'bar', next: null } } }
hook 執行的關鍵程式碼在於
memoizedState
和next
,其他(如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