在日常開發中,我們經常用到 React 的狀態管理 Hook:useState 和 useReducer。
但你有沒有想過:這些 Hook 內部是怎么實現的?為什么調用 setState
之后組件會重新渲染?
今天我們就來從零手寫 useState 和 useReducer,理解它們的原理。
一、React Hook 的本質
在 React 中,每個函數組件調用時都會按照 調用順序 來記錄 Hook。
比如下面的例子:
function Counter() {const [count, setCount] = useState(0);const [name, setName] = useState("張三");return (<div><p>{count}</p><p>{name}</p></div>);
}
React 內部會維護一個 數組(或鏈表) 來存儲這些 Hook 對應的狀態:
hooks = [{ state: 0 }, // count{ state: "張三" } // name
]
- 每次組件渲染時,
useState
都會返回這個位置對應的狀態。 - 調用
setState
時,會更新狀態并觸發組件重新渲染。
所以:Hook 的核心 = 存儲狀態 + 按順序取出。
二、手寫 useState
我們先寫一個最簡版的 React Hook 運行環境:
let hookStates = []; // 存放所有 hook 的狀態
let hookIndex = 0; // 當前執行到第幾個 hookfunction useState(initialValue) {const currentIndex = hookIndex; // 記錄當前 hook 的位置// 第一次渲染:存儲初始值if (hookStates[currentIndex] === undefined) {hookStates[currentIndex] = initialValue;}// 獲取當前狀態const state = hookStates[currentIndex];// 更新函數const setState = (newValue) => {hookStates[currentIndex] =typeof newValue === "function"? newValue(hookStates[currentIndex]): newValue;render(); // 觸發重新渲染};hookIndex++; // 下一個 hook 位置return [state, setState];
}
這里我們做了幾件事:
- 用
hookStates
保存所有狀態。 - 用
hookIndex
保證按順序存取。 setState
更新狀態后調用render
,模擬 React 重新渲染。
三、測試 useState
寫一個小 demo:
function Counter() {const [count, setCount] = useState(0);const [name, setName] = useState("張三");console.log("render:", { count, name });return {add: () => setCount(count + 1),rename: () => setName("李四"),};
}// 模擬渲染
function render() {hookIndex = 0; // 每次渲染重置app = Counter();
}let app;
render();// 測試
app.add(); // count 從 0 → 1
app.rename(); // name 從 "張三" → "李四"
運行過程:
render: { count: 0, name: '張三' }
render: { count: 1, name: '張三' }
render: { count: 1, name: '李四' }
? 我們的 useState
成功模擬了 React 的狀態管理!
四、手寫 useReducer
useReducer
的本質就是 useState
的加強版:
useState
直接存值useReducer
用 reducer 函數 來更新值
我們來實現它:
function useReducer(reducer, initialValue) {const [state, setState] = useState(initialValue);function dispatch(action) {const newState = reducer(state, action);setState(newState);}return [state, dispatch];
}
是不是很眼熟?
其實 React 源碼里 useState
就是 useReducer
的語法糖:
function useState(initialValue) {return useReducer((state, action) => action, initialValue);
}
五、測試 useReducer
來寫一個計數器:
function reducer(state, action) {switch (action.type) {case "add":return state + 1;case "sub":return state - 1;default:return state;}
}function Counter2() {const [count, dispatch] = useReducer(reducer, 0);console.log("render:", { count });return {add: () => dispatch({ type: "add" }),sub: () => dispatch({ type: "sub" }),};
}// 模擬渲染
function render() {hookIndex = 0;app = Counter2();
}let app;
render();app.add(); // count: 0 → 1
app.add(); // count: 1 → 2
app.sub(); // count: 2 → 1
輸出:
render: { count: 0 }
render: { count: 1 }
render: { count: 2 }
render: { count: 1 }
完美模擬 ?
六、總結
- useState:存儲值,返回
[state, setState]
。 - useReducer:存儲值 + reducer 邏輯,返回
[state, dispatch]
。 - 關系:
useState
=useReducer
的簡化版。
什么時候用哪個?
- 邏輯簡單(計數器、表單輸入) → 用
useState
- 邏輯復雜(多個狀態、復雜操作) → 用
useReducer
結語
通過手寫,我們發現 React Hook 并沒有什么黑魔法:
它只是 按照調用順序存儲狀態,并在更新時觸發重新渲染。
理解了原理,再寫業務代碼時就更清晰:
為什么 Hook 不能寫在 if/for 里?為什么每次渲染必須順序一致?
—— 因為 React 就是用數組來按順序存儲的