用useState实现state和setState功能
面试速答
- 这题通常不是让你实现完整 React,而是考你是否理解 Hooks 为什么依赖调用顺序。
- 一个极简版
useState至少要解决两件事:- 状态存在哪里
- 每次调用怎么拿回属于自己的那一格状态
极简实现思路
最经典的思路是:
- 用数组保存所有 Hook 状态
- 用游标记录当前执行到第几个 Hook
- 组件每次重新执行前把游标重置为 0
示例代码
let hookStates: unknown[] = []
let hookIndex = 0
function useState<T>(initialState: T) {
const currentIndex = hookIndex
if (hookStates[currentIndex] === undefined) {
hookStates[currentIndex] = initialState
}
function setState(nextState: T | ((prev: T) => T)) {
const prevState = hookStates[currentIndex] as T
hookStates[currentIndex] =
typeof nextState === 'function'
? (nextState as (prev: T) => T)(prevState)
: nextState
render()
}
const state = hookStates[currentIndex] as T
hookIndex++
return [state, setState] as const
}
function Counter() {
const [count, setCount] = useState(0)
return {
click() {
setCount(c => c + 1)
},
value: count,
}
}
function render() {
hookIndex = 0
const app = Counter()
console.log(app.value)
return app
}
这段代码说明了什么
1. Hook 状态不是存在函数局部变量里
组件函数每次执行都会重新跑一遍,所以状态必须存在函数外部的某个稳定容器里。
2. Hook 依赖调用顺序
第一次调用 useState 取数组第 0 项,第二次取第 1 项。只要顺序一变,状态槽位就会错位。
3. setState 要记住自己的槽位
所以代码里要先保存 currentIndex,不能直接用会变化的全局 hookIndex。
真正 React 还多做了什么
真实 React 远比这个复杂,还包括:
- Fiber 节点上的 Hook 链表
- 更新队列
- 批处理
- 优先级调度
- 惰性初始化
- 跳过相同值更新
但面试里把这层核心机制说明白,通常就够了。
标准答法
问:为什么 useState 一定依赖调用顺序?
答:因为极简模型下,React 需要通过“第几个 Hook 调用”来找到对应状态槽位。如果条件分支改变了调用顺序,状态映射就会错乱。
易错点
- 把状态存在组件函数局部变量里。
setState闭包没保存当前索引。- 没有在重新渲染前重置游标。
速记要点
- 数组/链表存状态,游标定位槽位。
- 每次 render 前重置游标。
- 这也是 Hooks 必须固定调用顺序的原因。