让小龙虾给我写文章

46 阅读5分钟

React useState 源码深度解析

前言

useState 是 React Hooks 中最常用的 Hook 之一。本文将深入源码,揭示 useState 的实现原理和工作机制。

目录

  1. useState 基础用法
  2. 源码位置与结构
  3. 核心实现原理
  4. 首次渲染流程
  5. 更新流程
  6. Fiber 架构中的 Hooks
  7. 常见问题解析

基础用法

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

源码结构

源码位置

在 React 源码中,Hooks 相关代码主要在以下位置:

react/
├── packages/
│   ├── react/
│   │   └── src/
│   │       └── ReactHooks.js          # useState 入口
│   └── react-reconciler/
│       └── src/
│           ├── ReactFiberHooks.js     # Hooks 核心实现
│           └── ReactFiberWorkLoop.js  # 调度逻辑

入口代码

// packages/react/src/ReactHooks.js

export function useState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

核心原理

1. Dispatcher 机制

React 使用 Dispatcher 模式来管理 Hooks。根据组件所处的不同阶段,会使用不同的 dispatcher:

// packages/react-reconciler/src/ReactFiberHooks.js

// 首次渲染时的 dispatcher
const HooksDispatcherOnMount = {
  useState: mountState,
  useEffect: mountEffect,
  // ...
};

// 更新时的 dispatcher
const HooksDispatcherOnUpdate = {
  useState: updateState,
  useEffect: updateEffect,
  // ...
};

2. Hook 数据结构

每个 Hook 会创建一个 hook 对象:

type Hook = {
  memoizedState: any,        // 当前状态值
  baseState: any,            // 基础状态
  baseQueue: Update<any>,    // 基础更新队列
  queue: UpdateQueue<any>,   // 更新队列
  next: Hook | null,         // 指向下一个 Hook(链表结构)
};

3. Fiber 节点与 Hooks 链表

每个 Fiber 节点上维护着一个 Hooks 链表:

// Fiber 节点结构(简化)
type Fiber = {
  memoizedState: Hook | null,  // 指向第一个 Hook
  updateQueue: any,
  // ...
};

链表结构示意:

Fiber.memoizedState
  ↓
Hook1 (useState) → Hook2 (useEffect) → Hook3 (useState) → null

首次渲染流程

mountState 实现

// packages/react-reconciler/src/ReactFiberHooks.js

function mountState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  // 1. 创建 Hook 对象并加入链表
  const hook = mountWorkInProgressHook();
  
  // 2. 处理初始状态(支持函数)
  if (typeof initialState === 'function') {
    initialState = initialState();
  }
  
  // 3. 保存初始状态
  hook.memoizedState = hook.baseState = initialState;
  
  // 4. 创建更新队列
  const queue = (hook.queue = {
    pending: null,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: initialState,
  });
  
  // 5. 创建 dispatch 函数(绑定当前 Fiber 和 queue)
  const dispatch = (queue.dispatch = dispatchSetState.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ));
  
  // 6. 返回 [state, setState]
  return [hook.memoizedState, dispatch];
}

mountWorkInProgressHook

function mountWorkInProgressHook(): Hook {
  const hook: Hook = {
    memoizedState: null,
    baseState: null,
    baseQueue: null,
    queue: null,
    next: null,
  };

  if (workInProgressHook === null) {
    // 第一个 Hook
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    // 后续 Hook,加入链表尾部
    workInProgressHook = workInProgressHook.next = hook;
  }
  
  return workInProgressHook;
}

更新流程

updateState 实现

function updateState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  // updateState 实际上调用 updateReducer
  return updateReducer(basicStateReducer, initialState);
}

updateReducer 核心逻辑

function updateReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  // 1. 获取当前 Hook
  const hook = updateWorkInProgressHook();
  const queue = hook.queue;
  
  // 2. 获取更新队列
  const pending = queue.pending;
  
  if (pending !== null) {
    // 3. 处理所有待处理的更新
    const first = pending.next;
    let newState = hook.baseState;
    let update = first;
    
    do {
      // 计算新状态
      const action = update.action;
      newState = reducer(newState, action);
      update = update.next;
    } while (update !== first);
    
    // 4. 更新 Hook 状态
    hook.memoizedState = newState;
    hook.baseState = newState;
    queue.pending = null;
  }
  
  // 5. 返回新状态和 dispatch
  const dispatch = queue.dispatch;
  return [hook.memoizedState, dispatch];
}

basicStateReducer

function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
  // 支持函数式更新:setState(prev => prev + 1)
  return typeof action === 'function' ? action(state) : action;
}

Dispatch 函数:dispatchSetState

function dispatchSetState<S, A>(
  fiber: Fiber,
  queue: UpdateQueue<S, A>,
  action: A,
) {
  // 1. 创建 update 对象
  const update: Update<S, A> = {
    action,
    next: null,
  };
  
  // 2. 将 update 加入队列(环形链表)
  const pending = queue.pending;
  if (pending === null) {
    // 第一个更新,指向自己形成环
    update.next = update;
  } else {
    update.next = pending.next;
    pending.next = update;
  }
  queue.pending = update;
  
  // 3. 调度更新(触发重新渲染)
  scheduleUpdateOnFiber(fiber);
}

Fiber 架构中的 Hooks

为什么 Hooks 必须按顺序调用?

因为 React 依赖 调用顺序 来匹配 Hooks 与 Fiber 节点上的链表:

// ❌ 错误:条件调用会打乱顺序
function Component() {
  if (condition) {
    const [a, setA] = useState(0);  // 有时是第一个,有时不存在
  }
  const [b, setB] = useState(0);    // 位置不稳定
}

// ✅ 正确:顺序固定
function Component() {
  const [a, setA] = useState(0);    // 始终是第一个
  const [b, setB] = useState(0);    // 始终是第二个
}

Hooks 链表遍历

function updateWorkInProgressHook(): Hook {
  // 从 Fiber 的 memoizedState 链表中取出对应 Hook
  if (currentHook === null) {
    const current = currentlyRenderingFiber.alternate;
    currentHook = current.memoizedState;
  } else {
    currentHook = currentHook.next;
  }
  
  // 复制到 workInProgress 树
  const newHook: Hook = {
    memoizedState: currentHook.memoizedState,
    baseState: currentHook.baseState,
    baseQueue: currentHook.baseQueue,
    queue: currentHook.queue,
    next: null,
  };
  
  // 加入新的链表
  if (workInProgressHook === null) {
    workInProgressHook = newHook;
    currentlyRenderingFiber.memoizedState = newHook;
  } else {
    workInProgressHook = workInProgressHook.next = newHook;
  }
  
  return workInProgressHook;
}

常见问题解析

1. 为什么 setState 是异步的?

function Component() {
  const [count, setCount] = useState(0);
  
  const handleClick = () => {
    setCount(count + 1);
    console.log(count);  // 仍然是旧值!
  };
}

原因:

  • setState 只是将更新加入队列,不会立即修改状态
  • React 会批量处理更新(batch updates),在合适时机统一重新渲染
  • 优化性能,避免不必要的多次渲染

解决方案:

// 使用函数式更新保证基于最新值
setCount(prevCount => prevCount + 1);

2. 为什么多次 setState 只生效一次?

const handleClick = () => {
  setCount(count + 1);  // count = 0 => 1
  setCount(count + 1);  // count = 0 => 1(仍基于旧值)
  setCount(count + 1);  // count = 0 => 1
};
// 结果:count = 1

原因:

  • 三次 setCount 都是基于渲染时的闭包值 count = 0
  • 等价于 setCount(1) 执行了三次

解决方案:

const handleClick = () => {
  setCount(c => c + 1);  // 0 => 1
  setCount(c => c + 1);  // 1 => 2
  setCount(c => c + 1);  // 2 => 3
};
// 结果:count = 3

3. useState 的初始值只执行一次?

function Component() {
  const [state, setState] = useState(expensiveCalculation());
  // expensiveCalculation 每次渲染都会执行!
}

// ✅ 惰性初始化
function Component() {
  const [state, setState] = useState(() => expensiveCalculation());
  // 只在首次渲染时执行一次
}

4. 对象/数组更新不生效?

// ❌ 错误:直接修改原对象
const [obj, setObj] = useState({ count: 0 });
obj.count = 1;
setObj(obj);  // React 检测不到变化(引用相同)

// ✅ 正确:创建新对象
setObj({ ...obj, count: 1 });

总结

useState 核心要点

  1. Dispatcher 模式:根据渲染阶段选择不同的实现
  2. 链表结构:多个 Hooks 通过链表连接在 Fiber 节点上
  3. 更新队列:setState 将更新加入环形链表,批量处理
  4. 顺序依赖:依赖调用顺序匹配 Hook,不能条件调用
  5. 函数式更新:支持 setState(prev => next) 避免闭包陷阱

实现流程图

用户调用 useState(0)
    ↓
首次渲染:mountState
    ↓
创建 Hook 对象 → 加入链表 → 创建 dispatch 函数
    ↓
返回 [state, setState]
    ↓
用户调用 setState(1)
    ↓
dispatchSetState:创建 update → 加入队列 → 触发调度
    ↓
重新渲染:updateState
    ↓
updateReducer:处理队列 → 计算新状态 → 返回新值

参考资料


作者: 哈基米 (部署小龙虾让ai写的文章)

日期: 2026-03-11

关键词: React, useState, Hooks, 源码分析, Fiber