跳到主要内容

用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 必须固定调用顺序的原因。