React Hooks底層執行邏輯詳解
React Hooks 在表面上看像普通的函數調用,背后卻隱藏著一套復雜而高效的運行時機制。要理解 React Hooks 的底層執行邏輯,需要從 React 如何管理組件的狀態與副作用入手。
🧠 一、React 為什么引入 Hooks?
在 class 組件中,狀態邏輯分散、復用困難(通過 HOC、render props 實現)。Hooks 通過“函數組件 + 閉包 + 順序調用”機制,解決了這些問題,讓函數組件擁有“類”的能力。
🔍 二、Hooks 的核心機制:調用順序與數組棧
? 核心思想:
React 內部為每個組件實例維護一個 “Hook 調用棧”(本質上是一個數組),Hooks 的執行順序嚴格依賴于你在函數中書寫的順序。
舉個例子:
function MyComponent() {const [count, setCount] = useState(0) // 第一個 Hook(索引 0)const [name, setName] = useState('') // 第二個 Hook(索引 1)
}
React 內部邏輯可以抽象為偽代碼:
let hookIndex = 0
const hooks = []function useState(initialValue) {const currentIndex = hookIndexif (isMount) {hooks[currentIndex] = typeof initialValue === 'function' ? initialValue() : initialValue}const setState = (newValue) => {hooks[currentIndex] = newValuetriggerComponentUpdate()}hookIndex++return [hooks[currentIndex], setState]
}
?? 所以:每次重新渲染組件時,React 都會以相同順序重新執行
useXXX()
,來匹配狀態數組中的值。
🔧 三、Hooks 的執行流程(React 內部機制)
🧩 每次組件渲染時:
-
當前組件會進入 render phase
-
React 初始化
fiberNode.memoizedState
(Hook 存儲區) -
每次調用一個 Hook(如
useState
、useEffect
):- React 用當前的
hookIndex
取出對應位置的值 - 更新完后
hookIndex++
- React 用當前的
-
所有 Hook 調用完畢后:
memoizedState
就是這個組件的 Hook 狀態鏈- React 將其掛在 fiber 樹上,供下一次渲染使用
?? 四、不同 Hook 的底層行為
1?? useState
- 在首次渲染時保存初始值
- 后續調用 setState 會觸發組件更新,并保留新值在狀態數組中
2?? useEffect
- 注冊副作用及清理函數
- 保存依賴數組,用于下一次渲染對比
- 在 commit 階段執行副作用
3?? useRef
- 保存一個
{ current: ... }
對象 - 是在 Hook 數組中創建的穩定引用,不會重新創建
4?? useMemo
/ useCallback
- 保存返回值或函數引用
- 對比依賴數組決定是否復用舊值
📊 五、Hook 狀態存儲結構:Fiber Node
每個組件的所有 Hook 狀態,都掛載在它自己的 Fiber Node 上的 memoizedState
字段中,它實際上是一個單鏈表結構。
FunctionComponentFiberNode
└── memoizedState --> HookState(useState) └── next --> HookState(useEffect) └── next --> ...
這個結構意味著:
- 每個 Hook 對應鏈表中的一個節點
- 遍歷順序必須與調用順序保持一致,否則狀態將錯位(所以 Hooks 不能寫在條件語句中)
🚨 六、為什么 Hooks 不能放在 if 語句里?
因為狀態順序是通過“調用順序 + 索引”來維護的。如果你這樣寫:
if (someCondition) {useState(...) // ?? 不一定會調用
}
那么下一次渲染時,Hook 的數量或順序可能變化,導致狀態錯亂(取到了別的 Hook 的狀態)!
React 會通過內部開發環境檢查這些不規范用法。
🧬 七、Hook 觸發組件更新的機制
當調用 setState(newVal)
:
- 會創建一個更新對象,加入到組件的更新隊列
- 標記當前 Fiber 為“需要更新”
- 觸發 React Scheduler 安排任務(基于優先級)
- 進入 render phase,重新執行組件函數(重新執行所有 Hooks)
- 比較 Fiber 樹 → 更新 DOM
🧪 八、一個模擬實現:useState
let hookStates = []
let hookIndex = 0function useState(initialValue) {const currentIndex = hookIndexhookStates[currentIndex] = hookStates[currentIndex] || initialValuefunction setState(newVal) {hookStates[currentIndex] = newValrender()}hookIndex++return [hookStates[currentIndex], setState]
}function render() {hookIndex = 0ReactDOM.render(<App />, document.getElementById('root'))
}
? 九、總結:Hooks 底層關鍵點
機制 | 說明 |
---|---|
順序執行原則 | Hooks 必須按照一致順序調用 |
狀態數組/鏈表結構 | 每個 Hook 都在 Fiber 節點的狀態鏈表中占一項 |
更新觸發原理 | setState 會觸發組件的調度和重新渲染 |
Hooks 本質 | React 自定義的一套狀態和副作用管理系統,依賴“閉包 + 引用 + 順序”維護狀態 |
自定義Hook
自定義 Hooks 是現代 React 中最重要的模式之一,用于在函數組件之間復用邏輯。它是一種在組件外提取公共邏輯的方式,優雅地替代了以前 class 組件中用 HOC 或 render props 的做法。
🧠 一、自定義 Hook 是什么?
自定義 Hook 就是一個以 use
開頭的 JavaScript 函數,內部可以調用其他 Hooks(如 useState、useEffect、useContext 等)。
它并不需要擁有特殊的語法,而是遵守命名規范(以 use
開頭)和 Hook 規則(只能在頂層和 React 函數組件中調用)。
🔧 二、基礎語法結構
function useMyHook() {const [state, setState] = useState(initialValue)// 可以包含副作用useEffect(() => {// 例如訂閱數據return () => {// 清理操作}}, [])return { state, setState }
}
使用方法:
function MyComponent() {const { state, setState } = useMyHook()return <div>{state}</div>
}
🛠? 三、常見自定義 Hook 示例
1?? useWindowSize:監聽窗口大小
import { useState, useEffect } from 'react'function useWindowSize() {const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight })useEffect(() => {const handleResize = () => {setSize({ width: window.innerWidth, height: window.innerHeight })}window.addEventListener('resize', handleResize)return () => window.removeEventListener('resize', handleResize)}, [])return size
}
2?? useFetch:封裝數據請求邏輯
import { useState, useEffect } from 'react'function useFetch(url) {const [data, setData] = useState(null)const [loading, setLoading] = useState(true)useEffect(() => {setLoading(true)fetch(url).then(res => res.json()).then(data => {setData(data)setLoading(false)})}, [url])return { data, loading }
}
使用:
const { data, loading } = useFetch('/api/user')
🧩 四、自定義 Hook 的使用場景
場景 | Hook 示例 |
---|---|
狀態共享 | useForm , useTheme , useAuth |
業務邏輯抽象 | usePagination , useLogin |
操作 DOM | useScroll , useFocus |
事件/副作用封裝 | useOnlineStatus , useDebounce |
狀態機器或流程控制 | useWizard , useStepper |
🚨 五、自定義 Hook 的注意事項
1. 必須以 use
開頭
否則 React 無法檢測是否遵守 Hook 的規則。
2. 遵循 Hook 規則(頂層調用)
- 不要在 if、for、事件中調用 Hook
- Hook 必須在函數組件或另一個 Hook 中使用
Hooks 是
按調用順序管理狀態
的,調用順序必須穩定
,執行上下文必須是“可追蹤”的(函數組件或 Hook)
,否則 React 無法正確管理它們。
3. 避免副作用重復(加依賴)
useEffect(() => {// fetch or something
}, [url]) // 依賴項必須寫全
? 六、自定義 Hook vs HOC/Render Props
特性 | 自定義 Hook | HOC | Render Props |
---|---|---|---|
寫法簡潔 | ? 非常簡潔 | ? 會嵌套 | ? 需要 props 回調 |
類型推導好 | ? 原生支持 | ? props 類型麻煩 | ? |
可組合性 | ? 任意組合 | ? | ? |
推薦程度 | ? 推薦使用 | ? 舊方式(不推薦) | ?(不推薦) |
📦 七、如何組織自定義 Hook?
建議創建一個 hooks/
文件夾,每個 Hook 一個文件。例如:
src/
├── hooks/
│ ├── useAuth.js
│ ├── useFetch.js
│ └── useWindowSize.js
? 總結
你應該使用自定義 Hook 的時候: |
---|
有重復邏輯(如監聽、請求、共享狀態等) |
邏輯獨立但不涉及 UI |
想增強可維護性和可測試性 |
如果你告訴我你項目中具體遇到的重復邏輯或問題,我可以幫你定制一個適合你項目的自定義 Hook 模板。是否要嘗試?
Fiber&Scheduler
Fiber 架構 + Scheduler 是 React 實現 并發渲染 的核心。它們分別負責「任務的組織與執行邏輯」和「任務的調度與執行時機」。
🧠 一、React Fiber 架構詳解
Fiber 是 React 16 引入的全新架構,用來替代舊的 Stack Reconciler。它的設計目標是:
- 任務可被拆分(增量渲染)
- 渲染可中斷、恢復、重用
- 支持并發調度
📦 1. Fiber 是什么?
一個 Fiber 就是一個組件的工作單元(Work Unit),它是一個 JavaScript 對象,描述了:
type FiberNode = {type: Function | string;key: string | null;child: FiberNode | null;sibling: FiberNode | null;return: FiberNode | null; // 父節點stateNode: any; // 組件實例memoizedProps: any;memoizedState: any;alternate: FiberNode | null; // 雙緩沖flags: number; // 副作用標記...
}
🧭 2. Fiber Tree 構建過程
每次更新時,React 會創建一棵新的 Fiber 樹(work-in-progress tree),由當前樹(current tree)復制并修改。
- 雙緩沖機制:
current
&workInProgress
交替使用 - 每個更新任務遞歸生成 Fiber 節點,構成完整 Fiber 樹
🔁 3. Fiber 的工作循環(核心階段)
🔨 Reconciliation(協調階段)
- 調用組件函數(或類的 render)生成新的虛擬 DOM
- 比較新舊 Fiber 樹,標記哪些節點需要變更
- 構建 “Effect List”(副作用鏈表)
🚀 Commit(提交階段)
- 根據 Effect List 執行真實的 DOM 操作(插入/刪除/更新)
- 不可被打斷,必須同步完成
🕹? 二、Scheduler 調度器詳解
Scheduler 是 React 的調度核心,負責 管理 Fiber 的執行時機與優先級,使 React 擁有「可中斷」和「可恢復」的能力。
📋 1. 核心能力
- 管理任務隊列
- 計算任務優先級(Lanes)
- 根據瀏覽器空閑時間切片執行
- 決定是否讓出主線程(
shouldYield
)
? 2. 時間切片(Time Slicing)
while (work && !shouldYield()) {work = performUnitOfWork(work);
}
- 每次只做一小部分工作(一個 Fiber 節點)
- 超出時間閾值就讓出執行權,避免卡住主線程
📊 3. 優先級系統(Lanes)
React 引入「Lanes(車道)」作為多優先級調度方案:
const NoLane = 0b00000
const SyncLane = 0b00001 // 同步優先級
const InputContinuousLane = 0b00010 // 用戶輸入優先
const DefaultLane = 0b00100
const IdleLane = 0b10000 // 最低優先
React 會根據任務類型分配 Lane,調度器根據當前空閑情況,調度最高優先的任務先執行。
?? 三、Fiber + Scheduler 協同流程圖
用戶觸發更新(如點擊按鈕)↓
Scheduler 收到任務,放入任務隊列↓
根據 Lane 決定優先級 & 是否立即執行↓
Fiber Tree 被構建(協調階段)↓
每個 Fiber 任務以時間切片形式執行↓
中途檢查 shouldYield(),必要時中斷↓
所有 Fiber 構建完成,進入 commit 階段↓
一次性提交 DOM 修改(同步執行)
🧩 總結:Fiber & Scheduler 分工
功能 | Fiber | Scheduler |
---|---|---|
結構 | 描述每個組件的渲染任務 | 管理任務執行的優先級和時機 |
拆分任務 | 將渲染工作拆成一個個 Fiber 節點 | 將任務切片處理,防止阻塞主線程 |
可中斷機制 | 通過時間切片,暫停和恢復渲染 | 決定何時中斷、恢復和重新調度任務 |
優先級處理 | 每個 Fiber 帶有優先級(lane) | 任務隊列按優先級排序 |
如果你需要我通過可視化圖解、源碼級解析(如 Scheduler 源碼調度流程),我也可以幫你補充。是否繼續?