在日常开发中,我们经常用到 React 的状态管理 Hook:useState 和 useReducer 。
但你有没有想过:这些 Hook 内部是怎么实现的?为什么调用 setState
之后组件会重新渲染?
今天我们就来从零手写 useState 和 useReducer,理解它们的原理。
一、React Hook 的本质
在 React 中,每个函数组件调用时都会按照 调用顺序 来记录 Hook。
比如下面的例子:
jsx
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 运行环境:
js
let hookStates = []; // 存放所有 hook 的状态
let hookIndex = 0; // 当前执行到第几个 hook
function 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:
js
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 函数 来更新值
我们来实现它:
js
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
的语法糖:
js
function useState(initialValue) {
return useReducer((state, action) => action, initialValue);
}
五、测试 useReducer
来写一个计数器:
js
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 就是用数组来按顺序存储的