实现 useState Hook

手写一个简易版 React useState Hook

问题

用原生 JavaScript 实现 React 的 useState Hook,支持 state 读取和 setState 更新功能。

解答

基础版本

// 存储所有 state 的数组
let states = [];
// 当前 state 的索引
let stateIndex = 0;

function useState(initialValue) {
  // 闭包保存当前索引
  const currentIndex = stateIndex;
  
  // 初始化 state(只在首次调用时生效)
  if (states[currentIndex] === undefined) {
    states[currentIndex] = initialValue;
  }
  
  // setState 函数
  const setState = (newValue) => {
    // 支持函数式更新
    if (typeof newValue === 'function') {
      states[currentIndex] = newValue(states[currentIndex]);
    } else {
      states[currentIndex] = newValue;
    }
    // 触发重新渲染
    render();
  };
  
  // 索引递增,为下一个 useState 准备
  stateIndex++;
  
  return [states[currentIndex], setState];
}

// 重置索引,模拟组件重新渲染
function resetIndex() {
  stateIndex = 0;
}

完整示例

let states = [];
let stateIndex = 0;

function useState(initialValue) {
  const currentIndex = stateIndex;
  
  if (states[currentIndex] === undefined) {
    states[currentIndex] = initialValue;
  }
  
  const setState = (newValue) => {
    if (typeof newValue === 'function') {
      states[currentIndex] = newValue(states[currentIndex]);
    } else {
      states[currentIndex] = newValue;
    }
    render();
  };
  
  stateIndex++;
  return [states[currentIndex], setState];
}

// 模拟组件
function Component() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('React');
  
  console.log(`count: ${count}, name: ${name}`);
  
  return { count, setCount, name, setName };
}

// 模拟渲染函数
function render() {
  stateIndex = 0; // 重置索引
  Component();
}

// 测试
const { setCount, setName } = Component();

setCount(1);        // 输出: count: 1, name: React
setCount(n => n + 1); // 输出: count: 2, name: React
setName('Vue');     // 输出: count: 2, name: Vue

使用 Map 的版本(更接近真实实现)

// 模拟 Fiber 节点
const fiber = {
  memoizedState: null, // 链表头
  stateNode: null,
};

let workInProgressHook = null;

function useState(initialValue) {
  const hook = {
    memoizedState: initialValue,
    next: null,
  };
  
  if (!fiber.memoizedState) {
    // 第一个 hook
    fiber.memoizedState = hook;
  } else {
    // 追加到链表末尾
    workInProgressHook.next = hook;
  }
  workInProgressHook = hook;
  
  const setState = (newValue) => {
    hook.memoizedState = typeof newValue === 'function' 
      ? newValue(hook.memoizedState) 
      : newValue;
    render();
  };
  
  return [hook.memoizedState, setState];
}

关键点

  • 闭包保存索引:每个 setState 通过闭包记住自己对应的 state 索引
  • 调用顺序固定:Hook 必须在组件顶层调用,不能在条件语句中使用,否则索引会错乱
  • 重渲染时重置索引:每次渲染前将 stateIndex 归零,保证 Hook 按顺序匹配
  • 支持函数式更新setState(prev => prev + 1) 可以获取最新状态
  • 真实 React 用链表:实际实现使用链表结构存储 Hook,挂载在 Fiber 节点上