用100行代码实现React useState钩子:多状态管理揭秘
本文通过实现mini React的useState钩子,深入解析函数组件中多状态管理的核心机制。
问题背景
在实现React的useState钩子时,初始版本仅支持单个状态变量:
jsx
function useState(initial) {
const stateHook = { state: initial };
function setState(action) {
stateHook.state = action;
// 触发重渲染...
}
return [stateHook.state, setState];
}
但当组件内使用多个useState时,状态会发生覆盖:
scss
function Foo() {
const [count] = useState(10);
const [bar] = useState("bar"); // 覆盖前一个状态
}
解决方案:状态数组管理
核心改造方案如下:
- 全局状态追踪:
jsx
let stateHooks = []; // 存储所有状态钩子
let stateHookIndex = 0; // 当前钩子索引
- 组件渲染时重置索引:
jsx
function updateFunctionComponent(fiber) {
stateHooks = [];
stateHookIndex = 0; // 每次渲染重置索引
// ...其他逻辑
}
- 多状态支持:
jsx
function useState(initial) {
const currentFiber = wipFiber;
const oldHook = currentFiber.alternate?.stateHooks?.[stateHookIndex];
const stateHook = {
state: oldHook ? oldHook.state : initial
};
stateHooks.push(stateHook);
currentFiber.stateHooks = stateHooks;
stateHookIndex++; // 索引递增
const setState = (action) => {
stateHook.state = action(stateHook.state);
// 触发重渲染...
};
return [stateHook.state, setState];
}
实现效果
改造后完美支持多状态:
jsx
function Foo() {
const [count, setCount] = useState(10);
const [bar, setBar] = useState("bar");
// 点击时同时更新两个状态
const handleClick = () => {
setCount(c => c + 1);
setBar(b => b + "bar");
};
}
核心原理
- 状态隔离:通过索引顺序管理多个状态
- 渲染一致性:每次渲染重置索引确保顺序稳定
- 状态持久化:通过fiber.alternate获取旧状态
- 批处理更新:状态更新后统一触发重渲染
总结
本文通过50行代码实现了React useState的核心逻辑,解决了多状态管理的核心挑战。关键在于使用数组结构和索引追踪来维护多个状态钩子,保证状态更新的准确性和隔离性。这种模式也奠定了其他React钩子(如useEffect)的实现基础,体现了函数式组件的设计精髓。