Skip to content

第7章 Hooks实现原理

本章将深入React Hooks的实现原理,理解Hooks的数据结构、Dispatcher机制、以及useState、useEffect等核心Hooks的源码实现。这是理解React函数组件状态管理的关键。

在React 16.8之前,函数组件是无状态的,只能用于展示UI。如果需要状态管理或生命周期方法,必须使用类组件。React 16.8引入了Hooks,彻底改变了这一局面。Hooks让函数组件拥有了状态、副作用、上下文等能力,使得函数组件成为React开发的主流方式。

为什么React要引入Hooks?Hooks是如何在函数组件中保存状态的?为什么Hooks必须在函数组件顶层调用?useState和useEffect是如何工作的?并发Hooks如何实现非阻塞更新?

本章将逐一解答这些问题,带你深入理解Hooks的设计与实现。


7.1 Hooks的数据结构

Hooks的核心是一个链表结构,每个Hook节点保存了该Hook的状态和更新队列。

7.1.1 Hook对象

每个Hook都对应一个Hook对象,定义在ReactFiberHooks.js中:

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:194-200(React 19.3.0)

export type Hook = {
  memoizedState: any,
  baseState: any,
  baseQueue: Update<any, any> | null,
  queue: any,
  next: Hook | null,
};

Hook对象的属性

  1. memoizedState:当前Hook的状态

    • 对于useState:保存state值
    • 对于useEffect:保存effect对象
    • 对于useMemo:保存缓存的值和依赖数组
    • 对于useRef:保存ref对象
  2. baseState:基础状态

    • 用于计算最终state的起点
    • 在有优先级跳过的更新时使用
  3. baseQueue:基础更新队列

    • 保存被跳过的低优先级更新
    • 在下次渲染时会重新应用
  4. queue:更新队列

    • 对于useState:保存dispatch创建的更新
    • 对于useEffect:保存effect的依赖数组
  5. next:指向下一个Hook

    • 形成Hook链表
    • 保证Hooks的调用顺序

7.1.2 Effect对象

useEffect、useLayoutEffect等副作用Hook使用Effect对象:

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:217-233(React 19.3.0)

type EffectInstance = {
  destroy: void | (() => void),
};

export type Effect = {
  tag: HookFlags,
  inst: EffectInstance,
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
  next: Effect,
};

Effect对象的属性

  1. tag:Effect的类型标记

    • HasEffect:需要执行
    • Passive:useEffect(异步执行)
    • Layout:useLayoutEffect(同步执行)
    • Insertion:useInsertionEffect(在DOM变更前执行)
  2. inst:Effect实例

    • destroy:清理函数
    • 在effect重新执行前调用
  3. create:effect函数

    • 用户传入的副作用函数
    • 返回值是清理函数
  4. deps:依赖数组

    • 用于判断是否需要重新执行effect
    • null表示每次都执行
  5. next:指向下一个Effect

    • 形成Effect环形链表
    • 保证effect的执行顺序

7.1.3 Hooks链表

Hooks在Fiber节点上以链表形式存储:

javascript
// Fiber节点的memoizedState指向第一个Hook
fiber.memoizedState = firstHook;

// Hook链表结构
firstHook -> secondHook -> thirdHook -> null

Hooks链表的特点

  1. 单向链表:每个Hook通过next指针连接
  2. 顺序固定:Hooks的调用顺序必须保持一致
  3. 存储在Fiber:通过fiber.memoizedState访问

为什么Hooks必须在顶层调用?

Hooks依赖调用顺序来匹配状态。如果Hooks在条件语句或循环中调用,顺序可能会变化,导致状态错乱。

jsx
// ✗ 错误:Hooks在条件语句中
function BadComponent({ condition }) {
  if (condition) {
    const [state, setState] = useState(0); // 顺序不固定
  }
  const [count, setCount] = useState(0);
  return <div>{count}</div>;
}

// ✓ 正确:Hooks在顶层
function GoodComponent({ condition }) {
  const [state, setState] = useState(0); // 顺序固定
  const [count, setCount] = useState(0);
  
  if (condition) {
    // 使用state
  }
  
  return <div>{count}</div>;
}

Hooks链表的构建过程

首次渲染:
1. 调用useState(0)
   - 创建Hook1,memoizedState = 0
   - fiber.memoizedState = Hook1

2. 调用useState(1)
   - 创建Hook2,memoizedState = 1
   - Hook1.next = Hook2

3. 调用useEffect(...)
   - 创建Hook3,memoizedState = Effect对象
   - Hook2.next = Hook3

结果:
fiber.memoizedState -> Hook1 -> Hook2 -> Hook3 -> null

更新时:
1. 调用useState(0)
   - 复用Hook1,读取memoizedState

2. 调用useState(1)
   - 复用Hook2,读取memoizedState

3. 调用useEffect(...)
   - 复用Hook3,比较deps决定是否执行

7.1.4 示例:Hooks链表图解

让我们通过一个完整的示例来理解Hooks链表的结构:

jsx
function Counter() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('React');
  const prevCount = useRef(0);
  
  useEffect(() => {
    prevCount.current = count;
  }, [count]);
  
  useEffect(() => {
    document.title = `${name}: ${count}`;
  }, [name, count]);
  
  return (
    <div>
      <p>{name}: {count}</p>
      <p>Previous: {prevCount.current}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

Hooks链表结构

详细结构

javascript
// Fiber节点
fiber = {
  memoizedState: Hook1, // 指向第一个Hook
  updateQueue: {
    lastEffect: Effect2, // Effect环形链表
  },
  // ... 其他属性
};

// Hook1: useState(0)
Hook1 = {
  memoizedState: 0,
  baseState: 0,
  baseQueue: null,
  queue: {
    pending: null,
    dispatch: setCount,
  },
  next: Hook2,
};

// Hook2: useState('React')
Hook2 = {
  memoizedState: 'React',
  baseState: 'React',
  baseQueue: null,
  queue: {
    pending: null,
    dispatch: setName,
  },
  next: Hook3,
};

// Hook3: useRef(0)
Hook3 = {
  memoizedState: { current: 0 },
  baseState: null,
  baseQueue: null,
  queue: null,
  next: Hook4,
};

// Hook4: useEffect(...)
Hook4 = {
  memoizedState: {
    tag: Passive | HasEffect,
    inst: { destroy: undefined },
    create: () => { prevCount.current = count; },
    deps: [0],
    next: Effect2,
  },
  baseState: null,
  baseQueue: null,
  queue: null,
  next: Hook5,
};

// Hook5: useEffect(...)
Hook5 = {
  memoizedState: {
    tag: Passive | HasEffect,
    inst: { destroy: undefined },
    create: () => { document.title = `${name}: ${count}`; },
    deps: ['React', 0],
    next: Effect1, // 环形链表,指回第一个Effect
  },
  baseState: null,
  baseQueue: null,
  queue: null,
  next: null,
};

Effect环形链表

Effect1 -> Effect2 -> Effect1 (环形)
   ↑                    ↓
   └────────────────────┘

fiber.updateQueue.lastEffect = Effect2

这种环形链表设计使得React可以从任意Effect开始遍历整个链表。


7.2 Dispatcher机制

Dispatcher是React Hooks的核心机制,它决定了每个Hook调用时应该执行哪个实现函数。

7.2.1 什么是Dispatcher

Dispatcher是一个对象,包含了所有Hook的实现函数。React根据组件的渲染阶段,切换不同的Dispatcher。

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:3870-3897(React 19.3.0)

export const ContextOnlyDispatcher: Dispatcher = {
  readContext,
  use,
  useCallback: throwInvalidHookError,
  useContext: throwInvalidHookError,
  useEffect: throwInvalidHookError,
  useImperativeHandle: throwInvalidHookError,
  useLayoutEffect: throwInvalidHookError,
  useInsertionEffect: throwInvalidHookError,
  useMemo: throwInvalidHookError,
  useReducer: throwInvalidHookError,
  useRef: throwInvalidHookError,
  useState: throwInvalidHookError,
  useDebugValue: throwInvalidHookError,
  useDeferredValue: throwInvalidHookError,
  useTransition: throwInvalidHookError,
  useSyncExternalStore: throwInvalidHookError,
  useId: throwInvalidHookError,
  // ... 其他Hooks
};

为什么需要Dispatcher?

  1. 不同阶段不同行为:首次渲染和更新时,Hook的行为不同
  2. 错误检测:在非渲染阶段调用Hook会抛出错误
  3. 开发环境增强:开发环境可以添加额外的检查和警告

7.2.2 三种Dispatcher

React有三种主要的Dispatcher:

1. ContextOnlyDispatcher

在组件渲染之外使用,所有Hook都会抛出错误。

javascript
const ContextOnlyDispatcher: Dispatcher = {
  useState: throwInvalidHookError,
  useEffect: throwInvalidHookError,
  // ... 其他Hooks都抛出错误
};

function throwInvalidHookError() {
  throw new Error(
    'Invalid hook call. Hooks can only be called inside of the body of a function component.'
  );
}

使用场景

  • 组件渲染完成后
  • 事件处理函数中
  • 异步回调中
jsx
// ✗ 错误:在事件处理函数中调用Hook
function BadComponent() {
  const handleClick = () => {
    const [state, setState] = useState(0); // 抛出错误
  };
  
  return <button onClick={handleClick}>Click</button>;
}

2. HooksDispatcherOnMount

首次渲染时使用,创建新的Hook对象。

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:3898-3925(React 19.3.0)

const HooksDispatcherOnMount: Dispatcher = {
  readContext,
  use,
  useCallback: mountCallback,
  useContext: readContext,
  useEffect: mountEffect,
  useImperativeHandle: mountImperativeHandle,
  useLayoutEffect: mountLayoutEffect,
  useInsertionEffect: mountInsertionEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState,
  useDebugValue: mountDebugValue,
  useDeferredValue: mountDeferredValue,
  useTransition: mountTransition,
  useSyncExternalStore: mountSyncExternalStore,
  useId: mountId,
  // ... 其他Hooks
};

特点

  • 创建新的Hook对象
  • 初始化Hook的状态
  • 将Hook添加到链表

3. HooksDispatcherOnUpdate

更新时使用,复用已有的Hook对象。

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:3926-3953(React 19.3.0)

const HooksDispatcherOnUpdate: Dispatcher = {
  readContext,
  use,
  useCallback: updateCallback,
  useContext: readContext,
  useEffect: updateEffect,
  useImperativeHandle: updateImperativeHandle,
  useLayoutEffect: updateLayoutEffect,
  useInsertionEffect: updateInsertionEffect,
  useMemo: updateMemo,
  useReducer: updateReducer,
  useRef: updateRef,
  useState: updateState,
  useDebugValue: updateDebugValue,
  useDeferredValue: updateDeferredValue,
  useTransition: updateTransition,
  useSyncExternalStore: updateSyncExternalStore,
  useId: updateId,
  // ... 其他Hooks
};

特点

  • 复用已有的Hook对象
  • 读取Hook的状态
  • 处理更新队列

7.2.3 Dispatcher的切换

React在渲染函数组件时,会切换Dispatcher。

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:547-565(React 19.3.0)

function renderWithHooks(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  props: any,
  secondArg: any,
  nextRenderLanes: Lanes,
): any {
  // 1. 设置当前渲染的Fiber
  currentlyRenderingFiber = workInProgress;
  
  // 2. 清空Hooks链表
  workInProgress.memoizedState = null;
  workInProgress.updateQueue = null;
  
  // 3. 根据是否首次渲染,选择Dispatcher
  ReactSharedInternals.H =
    current === null || current.memoizedState === null
      ? HooksDispatcherOnMount  // 首次渲染
      : HooksDispatcherOnUpdate; // 更新
  
  // 4. 执行函数组件
  let children = Component(props, secondArg);
  
  // 5. 渲染完成,切换到ContextOnlyDispatcher
  ReactSharedInternals.H = ContextOnlyDispatcher;
  
  return children;
}

Dispatcher切换流程

示例:Dispatcher切换过程

jsx
function Counter() {
  // 此时Dispatcher是HooksDispatcherOnMount或HooksDispatcherOnUpdate
  const [count, setCount] = useState(0);
  
  const handleClick = () => {
    // 此时Dispatcher是ContextOnlyDispatcher
    // 如果在这里调用useState,会抛出错误
    setCount(count + 1); // 正确:调用dispatch函数,不是Hook
  };
  
  return <button onClick={handleClick}>{count}</button>;
}

首次渲染的流程

1. renderWithHooks开始
2. 设置ReactSharedInternals.H = HooksDispatcherOnMount
3. 执行Counter()
4. 调用useState(0)
   - 实际调用mountState(0)
   - 创建Hook对象
   - 返回[0, dispatch]
5. 执行完成
6. 设置ReactSharedInternals.H = ContextOnlyDispatcher
7. 返回children

更新时的流程

1. renderWithHooks开始
2. 设置ReactSharedInternals.H = HooksDispatcherOnUpdate
3. 执行Counter()
4. 调用useState(0)
   - 实际调用updateState(0)
   - 复用Hook对象
   - 处理更新队列
   - 返回[newState, dispatch]
5. 执行完成
6. 设置ReactSharedInternals.H = ContextOnlyDispatcher
7. 返回children

7.2.4 服务端Hooks的特殊处理

在服务端渲染(SSR)时,React使用不同的Hooks实现。

javascript
// 文件:packages/react-server/src/ReactFizzHooks.js

// 服务端的Hook对象更简单
type Hook = {
  memoizedState: any,
  queue: UpdateQueue<any> | null,
  next: Hook | null,
};

// 服务端的Dispatcher
const HooksDispatcher: Dispatcher = {
  readContext,
  use,
  useCallback: useCallback,
  useContext: useContext,
  useEffect: useEffect, // 服务端不执行
  useImperativeHandle: useImperativeHandle, // 服务端不执行
  useLayoutEffect: useLayoutEffect, // 服务端不执行
  useInsertionEffect: useInsertionEffect, // 服务端不执行
  useMemo: useMemo,
  useReducer: useReducer,
  useRef: useRef,
  useState: useState,
  // ... 其他Hooks
};

服务端Hooks的特点

  1. useEffect不执行:服务端没有副作用
  2. useState返回初始值:服务端只渲染一次
  3. useRef返回固定对象:服务端不需要持久化引用
  4. useMemo总是计算:服务端没有缓存

示例:服务端useState实现

javascript
// 文件:packages/react-server/src/ReactFizzHooks.js

function useState<S>(initialState: (() => S) | S): [S, Dispatch<S>] {
  // 服务端useState只返回初始值,dispatch是空函数
  return useReducer(
    basicStateReducer,
    typeof initialState === 'function' ? initialState() : initialState,
  );
}

function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
  return typeof action === 'function' ? action(state) : action;
}

// dispatch在服务端是空函数
function noop() {}

为什么服务端Hooks不同?

  1. 单次渲染:服务端只渲染一次,不需要更新机制
  2. 无副作用:服务端没有DOM,不需要执行effect
  3. 性能优化:简化实现,提高渲染速度
  4. Hydration准备:生成的HTML需要与客户端匹配

服务端与客户端Hooks对比

Hook服务端客户端
useState返回初始值处理更新队列
useEffect不执行异步执行
useLayoutEffect不执行同步执行
useMemo总是计算缓存结果
useCallback总是返回新函数缓存函数
useRef返回固定对象持久化引用
useContext读取context读取context
useReducer返回初始state处理更新队列

7.3 useState源码分析

useState是最常用的Hook,它让函数组件拥有状态管理能力。

7.3.1 mountState挂载阶段

首次渲染时,useState调用mountState函数:

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:1894-1920(React 19.3.0)

function mountStateImpl<S>(initialState: (() => S) | S): Hook {
  // 1. 创建Hook对象
  const hook = mountWorkInProgressHook();
  
  // 2. 处理初始state(支持函数形式)
  if (typeof initialState === 'function') {
    const initialStateInitializer = initialState;
    initialState = initialStateInitializer();
  }
  
  // 3. 设置初始state
  hook.memoizedState = hook.baseState = initialState;
  
  // 4. 创建更新队列
  const queue: UpdateQueue<S, BasicStateAction<S>> = {
    pending: null,
    lanes: NoLanes,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: initialState,
  };
  hook.queue = queue;
  
  return hook;
}

function mountState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  const hook = mountStateImpl(initialState);
  const queue = hook.queue;
  
  // 5. 创建dispatch函数
  const dispatch: Dispatch<BasicStateAction<S>> = 
    dispatchSetState.bind(null, currentlyRenderingFiber, queue);
  queue.dispatch = dispatch;
  
  // 6. 返回[state, dispatch]
  return [hook.memoizedState, dispatch];
}

mountState的步骤

  1. 创建Hook对象:调用mountWorkInProgressHook
  2. 处理初始state:如果是函数,执行函数获取初始值
  3. 设置初始state:memoizedState和baseState都设为初始值
  4. 创建更新队列:用于保存setState创建的更新
  5. 创建dispatch函数:绑定fiber和queue
  6. 返回结果:[state, dispatch]

mountWorkInProgressHook

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:979-1000(React 19.3.0)

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;
}

示例:首次渲染useState

jsx
function Counter() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('React');
  
  return <div>{name}: {count}</div>;
}

// 首次渲染流程:
// 1. 调用useState(0)
//    - mountState(0)
//    - 创建Hook1,memoizedState = 0
//    - 创建dispatch1
//    - 返回[0, dispatch1]

// 2. 调用useState('React')
//    - mountState('React')
//    - 创建Hook2,memoizedState = 'React'
//    - 创建dispatch2
//    - 返回['React', dispatch2]

// 结果:
// fiber.memoizedState -> Hook1 -> Hook2 -> null

7.3.2 updateState更新阶段

更新时,useState调用updateState函数:

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:1936-1940(React 19.3.0)

function updateState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  return updateReducer(basicStateReducer, initialState);
}

// basicStateReducer是useState的reducer
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
  return typeof action === 'function' ? action(state) : action;
}

updateState实际上调用updateReducer

useState是useReducer的特殊情况,reducer固定为basicStateReducer。

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:1100-1200(React 19.3.0,简化版)

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 baseQueue = hook.baseQueue;
    
    // 合并pending和baseQueue
    if (baseQueue !== null) {
      const baseFirst = baseQueue.next;
      const pendingFirst = pending.next;
      baseQueue.next = pendingFirst;
      pending.next = baseFirst;
    }
    
    hook.baseQueue = baseQueue = pending;
    queue.pending = null;
  }
  
  if (baseQueue !== null) {
    // 4. 遍历更新队列,计算新state
    const first = baseQueue.next;
    let newState = hook.baseState;
    
    let update = first;
    do {
      const updateLane = update.lane;
      
      if (!isSubsetOfLanes(renderLanes, updateLane)) {
        // 优先级不够,跳过这个更新
        // 保存到baseQueue,下次渲染时处理
        const clone: Update<S, A> = {
          lane: updateLane,
          action: update.action,
          hasEagerState: update.hasEagerState,
          eagerState: update.eagerState,
          next: null,
        };
        // 添加到新的baseQueue
      } else {
        // 优先级足够,应用这个更新
        if (update.hasEagerState) {
          // 使用预计算的state
          newState = update.eagerState;
        } else {
          // 调用reducer计算新state
          const action = update.action;
          newState = reducer(newState, action);
        }
      }
      
      update = update.next;
    } while (update !== null && update !== first);
    
    // 5. 保存新state
    hook.memoizedState = newState;
    hook.baseState = newBaseState;
    hook.baseQueue = newBaseQueueLast;
  }
  
  // 6. 返回[state, dispatch]
  const dispatch: Dispatch<A> = queue.dispatch;
  return [hook.memoizedState, dispatch];
}

updateWorkInProgressHook

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js

function updateWorkInProgressHook(): Hook {
  // 1. 移动currentHook指针
  let nextCurrentHook: null | Hook;
  if (currentHook === null) {
    const current = currentlyRenderingFiber.alternate;
    nextCurrentHook = current.memoizedState;
  } else {
    nextCurrentHook = currentHook.next;
  }
  currentHook = nextCurrentHook;
  
  // 2. 移动workInProgressHook指针
  let nextWorkInProgressHook: null | Hook;
  if (workInProgressHook === null) {
    nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
  } else {
    nextWorkInProgressHook = workInProgressHook.next;
  }
  
  if (nextWorkInProgressHook !== null) {
    // 复用已有的Hook
    workInProgressHook = nextWorkInProgressHook;
    nextWorkInProgressHook = workInProgressHook.next;
    currentHook = nextCurrentHook;
  } else {
    // 克隆currentHook
    const newHook: Hook = {
      memoizedState: currentHook.memoizedState,
      baseState: currentHook.baseState,
      baseQueue: currentHook.baseQueue,
      queue: currentHook.queue,
      next: null,
    };
    
    if (workInProgressHook === null) {
      currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
    } else {
      workInProgressHook = workInProgressHook.next = newHook;
    }
  }
  
  return workInProgressHook;
}

示例:更新useState

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

// 更新流程:
// 1. 用户点击按钮
// 2. 调用setCount(1)
//    - 创建update对象
//    - 加入queue.pending
//    - 调度更新

// 3. 重新渲染Counter
// 4. 调用useState(0)
//    - updateState(0)
//    - updateReducer(basicStateReducer, 0)
//    - 获取Hook对象
//    - 处理更新队列
//    - 计算新state: basicStateReducer(0, 1) = 1
//    - 返回[1, dispatch]

// 5. 渲染新UI

7.3.3 dispatchSetState触发更新

当调用setState时,实际上调用的是dispatchSetState函数:

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:3598-3627(React 19.3.0)

function dispatchSetState<S, A>(
  fiber: Fiber,
  queue: UpdateQueue<S, A>,
  action: A,
): void {
  // 1. 请求更新lane
  const lane = requestUpdateLane(fiber);
  
  // 2. 调用内部实现
  const didScheduleUpdate = dispatchSetStateInternal(
    fiber,
    queue,
    action,
    lane,
  );
  
  if (didScheduleUpdate) {
    startUpdateTimerByLane(lane, 'setState()', fiber);
  }
  
  markUpdateInDevTools(fiber, lane, action);
}

function dispatchSetStateInternal<S, A>(
  fiber: Fiber,
  queue: UpdateQueue<S, A>,
  action: A,
  lane: Lane,
): boolean {
  // 1. 创建update对象
  const update: Update<S, A> = {
    lane,
    revertLane: NoLane,
    gesture: null,
    action,
    hasEagerState: false,
    eagerState: null,
    next: null,
  };
  
  // 2. 判断是否在渲染阶段
  if (isRenderPhaseUpdate(fiber)) {
    // 在渲染阶段调用setState,加入渲染阶段更新队列
    enqueueRenderPhaseUpdate(queue, update);
  } else {
    const alternate = fiber.alternate;
    
    // 3. 尝试eager state优化
    if (
      fiber.lanes === NoLanes &&
      (alternate === null || alternate.lanes === NoLanes)
    ) {
      // 队列为空,可以提前计算新state
      const lastRenderedReducer = queue.lastRenderedReducer;
      if (lastRenderedReducer !== null) {
        try {
          const currentState: S = queue.lastRenderedState;
          const eagerState = lastRenderedReducer(currentState, action);
          
          // 保存预计算的state
          update.hasEagerState = true;
          update.eagerState = eagerState;
          
          if (is(eagerState, currentState)) {
            // 新state与旧state相同,可以bailout
            enqueueConcurrentHookUpdateAndEagerlyBailout(
              fiber, queue, update
            );
            return false;
          }
        } catch (error) {
          // 计算失败,在render阶段重新计算
        }
      }
    }
    
    // 4. 将update加入队列
    const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
    
    if (root !== null) {
      // 5. 调度更新
      scheduleUpdateOnFiber(root, fiber, lane);
      entangleTransitionUpdate(root, queue, lane);
      return true;
    }
  }
  
  return false;
}

dispatchSetState的关键步骤

  1. 创建update对象:包含action和lane
  2. eager state优化:如果队列为空,提前计算新state
  3. bailout优化:如果新state与旧state相同,跳过更新
  4. 加入更新队列:将update添加到queue.pending
  5. 调度更新:触发React的调度系统

eager state优化

当队列为空时,React会提前计算新state。如果新state与旧state相同(使用Object.is比较),React可以跳过整个更新流程,这是一个重要的性能优化。

jsx
function Counter() {
  const [count, setCount] = useState(0);
  
  const handleClick = () => {
    setCount(0); // 设置相同的值
  };
  
  return <button onClick={handleClick}>{count}</button>;
}

// 执行流程:
// 1. 调用setCount(0)
// 2. 创建update对象,action = 0
// 3. 队列为空,计算eagerState
//    eagerState = basicStateReducer(0, 0) = 0
// 4. 比较:is(0, 0) = true
// 5. bailout,不触发更新
// 6. 组件不会重新渲染

更新队列的结构

javascript
// queue.pending是一个环形链表
queue.pending -> update3 -> update1 -> update2 -> update3 (环形)
                   ↑                              ↓
                   └──────────────────────────────┘

// pending指向最后一个update
// pending.next指向第一个update

示例:多次setState

jsx
function Counter() {
  const [count, setCount] = useState(0);
  
  const handleClick = () => {
    setCount(count + 1); // update1
    setCount(count + 1); // update2
    setCount(count + 1); // update3
  };
  
  return <button onClick={handleClick}>{count}</button>;
}

// 执行流程:
// 1. 调用setCount(count + 1)三次
// 2. 创建三个update对象
//    update1: action = 1
//    update2: action = 1
//    update3: action = 1
// 3. 形成环形链表
//    queue.pending -> update3 -> update1 -> update2 -> update3

// 4. 重新渲染
// 5. 处理更新队列
//    newState = 0
//    newState = basicStateReducer(0, 1) = 1
//    newState = basicStateReducer(1, 1) = 1
//    newState = basicStateReducer(1, 1) = 1
// 6. 最终count = 1(不是3!)

// 正确的做法:使用函数式更新
const handleClick = () => {
  setCount(c => c + 1); // update1: action = c => c + 1
  setCount(c => c + 1); // update2: action = c => c + 1
  setCount(c => c + 1); // update3: action = c => c + 1
};

// 执行流程:
// 1. 处理更新队列
//    newState = 0
//    newState = (c => c + 1)(0) = 1
//    newState = (c => c + 1)(1) = 2
//    newState = (c => c + 1)(2) = 3
// 2. 最终count = 3(正确!)

7.4 useEffect源码分析

useEffect是React中处理副作用的核心Hook,它在commit阶段异步执行。

7.4.1 Effect数据结构

useEffect使用Effect对象来保存副作用信息:

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:217-233(React 19.3.0)

type EffectInstance = {
  destroy: void | (() => void),
};

export type Effect = {
  tag: HookFlags,
  inst: EffectInstance,
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
  next: Effect,
};

Effect对象的属性

  1. tag:Effect的标记

    • HookHasEffect:需要执行
    • HookPassive:useEffect(异步)
    • HookLayout:useLayoutEffect(同步)
    • HookInsertion:useInsertionEffect(DOM变更前)
  2. inst:Effect实例,保存destroy函数

  3. create:用户传入的effect函数

  4. deps:依赖数组

  5. next:指向下一个Effect(环形链表)

7.4.2 mountEffect挂载阶段

首次渲染时,useEffect调用mountEffect:

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:2671-2693(React 19.3.0)

function mountEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  mountEffectImpl(
    PassiveEffect | PassiveStaticEffect,
    HookPassive,
    create,
    deps,
  );
}

function mountEffectImpl(
  fiberFlags: Flags,
  hookFlags: HookFlags,
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  // 1. 创建Hook对象
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  
  // 2. 标记Fiber的flags
  currentlyRenderingFiber.flags |= fiberFlags;
  
  // 3. 创建Effect对象并保存到Hook
  hook.memoizedState = pushSimpleEffect(
    HookHasEffect | hookFlags,
    createEffectInstance(),
    create,
    nextDeps,
  );
}

pushSimpleEffect

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js

function pushSimpleEffect(
  tag: HookFlags,
  inst: EffectInstance,
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): Effect {
  const effect: Effect = {
    tag,
    inst,
    create,
    deps,
    next: (null: any),
  };
  
  let componentUpdateQueue: null | FunctionComponentUpdateQueue =
    (currentlyRenderingFiber.updateQueue: any);
  
  if (componentUpdateQueue === null) {
    // 创建updateQueue
    componentUpdateQueue = createFunctionComponentUpdateQueue();
    currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
    componentUpdateQueue.lastEffect = effect.next = effect;
  } else {
    // 添加到Effect环形链表
    const lastEffect = componentUpdateQueue.lastEffect;
    if (lastEffect === null) {
      componentUpdateQueue.lastEffect = effect.next = effect;
    } else {
      const firstEffect = lastEffect.next;
      lastEffect.next = effect;
      effect.next = firstEffect;
      componentUpdateQueue.lastEffect = effect;
    }
  }
  
  return effect;
}

Effect环形链表

fiber.updateQueue.lastEffect -> Effect3 -> Effect1 -> Effect2 -> Effect3
                                   ↑                              ↓
                                   └──────────────────────────────┘

7.4.3 updateEffect更新阶段

更新时,useEffect调用updateEffect:

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js
// 行号:2695-2700(React 19.3.0)

function updateEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  updateEffectImpl(PassiveEffect, HookPassive, create, deps);
}

function updateEffectImpl(
  fiberFlags: Flags,
  hookFlags: HookFlags,
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  // 1. 获取当前Hook
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const effect: Effect = hook.memoizedState;
  const inst = effect.inst;
  
  // 2. 比较依赖数组
  if (currentHook !== null) {
    if (nextDeps !== null) {
      const prevEffect: Effect = currentHook.memoizedState;
      const prevDeps = prevEffect.deps;
      
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        // 依赖没变,不需要执行effect
        hook.memoizedState = pushSimpleEffect(
          hookFlags, // 没有HookHasEffect标记
          inst,
          create,
          nextDeps,
        );
        return;
      }
    }
  }
  
  // 3. 依赖变化,需要执行effect
  currentlyRenderingFiber.flags |= fiberFlags;
  
  hook.memoizedState = pushSimpleEffect(
    HookHasEffect | hookFlags, // 添加HookHasEffect标记
    inst,
    create,
    nextDeps,
  );
}

areHookInputsEqual(依赖比较)

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js

function areHookInputsEqual(
  nextDeps: Array<mixed>,
  prevDeps: Array<mixed> | null,
): boolean {
  if (prevDeps === null) {
    return false;
  }
  
  // 逐个比较依赖项(使用Object.is)
  for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
    if (is(nextDeps[i], prevDeps[i])) {
      continue;
    }
    return false;
  }
  
  return true;
}

示例:useEffect的执行时机

jsx
function Counter() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('React');
  
  useEffect(() => {
    console.log('Effect 1: count changed');
    return () => {
      console.log('Cleanup 1');
    };
  }, [count]);
  
  useEffect(() => {
    console.log('Effect 2: name changed');
    return () => {
      console.log('Cleanup 2');
    };
  }, [name]);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <button onClick={() => setName('Vue')}>Name: {name}</button>
    </div>
  );
}

// 首次渲染:
// 1. render阶段:创建两个Effect对象,都标记HookHasEffect
// 2. commit阶段:异步执行
//    - Effect 1: count changed
//    - Effect 2: name changed

// 点击Count按钮:
// 1. render阶段:
//    - Effect 1:deps变化,标记HookHasEffect
//    - Effect 2:deps没变,不标记HookHasEffect
// 2. commit阶段:
//    - Cleanup 1
//    - Effect 1: count changed

// 点击Name按钮:
// 1. render阶段:
//    - Effect 1:deps没变,不标记HookHasEffect
//    - Effect 2:deps变化,标记HookHasEffect
// 2. commit阶段:
//    - Cleanup 2
//    - Effect 2: name changed

7.4.4 Effect的执行时机

useEffect在commit阶段的layout阶段之后异步执行。

commit阶段的三个子阶段

  1. before mutation阶段:DOM变更前

    • 调度useEffect
  2. mutation阶段:执行DOM变更

    • 插入、更新、删除DOM
  3. layout阶段:DOM变更后

    • 执行useLayoutEffect
    • 更新ref
  4. 异步执行:layout阶段之后

    • 执行useEffect

flushPassiveEffects(执行useEffect)

javascript
// 文件:packages/react-reconciler/src/ReactFiberWorkLoop.js

function flushPassiveEffects(): boolean {
  // 1. 执行所有effect的cleanup函数
  commitPassiveUnmountEffects(root.current);
  
  // 2. 执行所有effect的create函数
  commitPassiveMountEffects(root, root.current);
  
  return true;
}

执行顺序

1. 所有组件的cleanup函数(深度优先)
2. 所有组件的effect函数(深度优先)

示例:Effect执行顺序

jsx
function Parent() {
  useEffect(() => {
    console.log('Parent effect');
    return () => console.log('Parent cleanup');
  });
  
  return <Child />;
}

function Child() {
  useEffect(() => {
    console.log('Child effect');
    return () => console.log('Child cleanup');
  });
  
  return <div>Child</div>;
}

// 首次渲染:
// Child effect
// Parent effect

// 更新:
// Child cleanup
// Parent cleanup
// Child effect
// Parent effect

// 卸载:
// Child cleanup
// Parent cleanup

useEffect vs useLayoutEffect

特性useEffectuseLayoutEffect
执行时机layout阶段之后,异步layout阶段,同步
阻塞渲染
适用场景数据获取、订阅DOM测量、同步更新
服务端不执行不执行
jsx
// useEffect:不阻塞渲染
function AsyncComponent() {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    fetch('/api/data')
      .then(res => res.json())
      .then(setData);
  }, []);
  
  return <div>{data}</div>;
}

// useLayoutEffect:阻塞渲染
function SyncComponent() {
  const ref = useRef();
  const [height, setHeight] = useState(0);
  
  useLayoutEffect(() => {
    // 同步测量DOM
    setHeight(ref.current.offsetHeight);
  }, []);
  
  return <div ref={ref} style={{ height }}></div>;
}

7.5 useMemo与useCallback

useMemo和useCallback是React的性能优化Hook,它们通过缓存计算结果和函数来避免不必要的重新计算。

7.5.1 记忆化实现

mountMemo

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js

function mountMemo<T>(
  nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  
  // 执行计算函数
  const nextValue = nextCreate();
  
  // 保存值和依赖
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

updateMemo

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js

function updateMemo<T>(
  nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  
  if (prevState !== null) {
    if (nextDeps !== null) {
      const prevDeps: Array<mixed> | null = prevState[1];
      
      // 比较依赖
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        // 依赖没变,返回缓存的值
        return prevState[0];
      }
    }
  }
  
  // 依赖变化,重新计算
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

mountCallback

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js

function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  
  // 直接保存函数和依赖
  hook.memoizedState = [callback, nextDeps];
  return callback;
}

updateCallback

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js

function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  
  if (prevState !== null) {
    if (nextDeps !== null) {
      const prevDeps: Array<mixed> | null = prevState[1];
      
      // 比较依赖
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        // 依赖没变,返回缓存的函数
        return prevState[0];
      }
    }
  }
  
  // 依赖变化,返回新函数
  hook.memoizedState = [callback, nextDeps];
  return callback;
}

7.5.2 依赖比较机制

useMemo和useCallback使用相同的依赖比较机制:

javascript
function areHookInputsEqual(
  nextDeps: Array<mixed>,
  prevDeps: Array<mixed> | null,
): boolean {
  if (prevDeps === null) {
    return false;
  }
  
  // 使用Object.is逐个比较
  for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
    if (is(nextDeps[i], prevDeps[i])) {
      continue;
    }
    return false;
  }
  
  return true;
}

Object.is vs ===

javascript
// Object.is的特殊情况
Object.is(+0, -0); // false
+0 === -0; // true

Object.is(NaN, NaN); // true
NaN === NaN; // false

// 其他情况相同
Object.is(1, 1); // true
Object.is('a', 'a'); // true
Object.is({}, {}); // false(不同引用)

示例:useMemo的使用

jsx
function ExpensiveComponent({ items }) {
  // ✓ 好:使用useMemo缓存计算结果
  const sortedItems = useMemo(() => {
    console.log('Sorting items...');
    return items.slice().sort((a, b) => a.value - b.value);
  }, [items]);
  
  return (
    <ul>
      {sortedItems.map(item => (
        <li key={item.id}>{item.value}</li>
      ))}
    </ul>
  );
}

// 执行流程:
// 首次渲染:
// - mountMemo
// - 执行排序
// - 保存结果和依赖

// items没变:
// - updateMemo
// - 比较依赖:areHookInputsEqual([items], [prevItems])
// - 依赖相同,返回缓存的sortedItems
// - 不执行排序

// items变化:
// - updateMemo
// - 比较依赖:依赖不同
// - 重新执行排序
// - 保存新结果

示例:useCallback的使用

jsx
function Parent() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('React');
  
  // ✗ 差:每次渲染都创建新函数
  const handleClick = () => {
    console.log(count);
  };
  
  // ✓ 好:使用useCallback缓存函数
  const handleClickMemo = useCallback(() => {
    console.log(count);
  }, [count]);
  
  return (
    <div>
      <Child onClick={handleClick} /> {/* 每次都重新渲染 */}
      <ChildMemo onClick={handleClickMemo} /> {/* count没变时不重新渲染 */}
    </div>
  );
}

const ChildMemo = React.memo(function Child({ onClick }) {
  console.log('Child render');
  return <button onClick={onClick}>Click</button>;
});

useMemo vs useCallback

javascript
// useMemo:缓存计算结果
const value = useMemo(() => computeExpensiveValue(a, b), [a, b]);

// useCallback:缓存函数
const callback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

// useCallback等价于:
const callback = useMemo(() => {
  return () => {
    doSomething(a, b);
  };
}, [a, b]);

何时使用useMemo和useCallback

  1. 计算开销大:排序、过滤大数组、复杂计算
  2. 引用相等性重要:传递给React.memo组件的props
  3. 作为其他Hook的依赖:useEffect、useMemo的deps
jsx
// ✓ 好:计算开销大
const sortedList = useMemo(() => {
  return hugeList.sort((a, b) => a - b);
}, [hugeList]);

// ✗ 差:计算开销小,不值得缓存
const sum = useMemo(() => a + b, [a, b]);

// ✓ 好:传递给React.memo组件
const MemoChild = React.memo(Child);
const handleClick = useCallback(() => {
  doSomething();
}, []);
<MemoChild onClick={handleClick} />

// ✗ 差:子组件没有使用React.memo
const handleClick = useCallback(() => {
  doSomething();
}, []);
<Child onClick={handleClick} />

// ✓ 好:作为useEffect的依赖
const fetchData = useCallback(() => {
  fetch('/api/data');
}, []);

useEffect(() => {
  fetchData();
}, [fetchData]);

性能陷阱

jsx
// ✗ 差:依赖是对象字面量,每次都不同
const value = useMemo(() => {
  return computeExpensiveValue(config);
}, [{ a: 1, b: 2 }]); // 每次都是新对象

// ✓ 好:依赖是原始值
const value = useMemo(() => {
  return computeExpensiveValue(a, b);
}, [a, b]);

// ✗ 差:依赖是数组字面量
const value = useMemo(() => {
  return computeExpensiveValue(items);
}, [[1, 2, 3]]); // 每次都是新数组

// ✓ 好:依赖是稳定的引用
const items = [1, 2, 3]; // 组件外部
const value = useMemo(() => {
  return computeExpensiveValue(items);
}, [items]);

7.6 并发Hooks

React 18引入了并发特性,useTransition和useDeferredValue是两个核心的并发Hook。

7.6.1 useTransition实现

useTransition允许将状态更新标记为非紧急的,React可以中断这些更新来处理更紧急的任务。

mountTransition

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js

function mountTransition(): [
  boolean,
  (callback: () => void, options?: StartTransitionOptions) => void,
] {
  const stateHook = mountStateImpl(false);
  const start = startTransition.bind(
    null,
    currentlyRenderingFiber,
    stateHook.queue,
    true,
    false,
  );
  const hook = mountWorkInProgressHook();
  hook.memoizedState = start;
  return [false, start];
}

updateTransition

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js

function updateTransition(): [
  boolean,
  (callback: () => void, options?: StartTransitionOptions) => void,
] {
  const [booleanOrThenable] = updateState(false);
  const hook = updateWorkInProgressHook();
  const start = hook.memoizedState;
  const isPending =
    typeof booleanOrThenable === 'boolean'
      ? booleanOrThenable
      : useThenable(booleanOrThenable);
  return [isPending, start];
}

startTransition

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js

function startTransition(
  fiber: Fiber,
  queue: UpdateQueue<boolean, BasicStateAction<boolean>>,
  pendingState: boolean,
  finishedState: boolean,
  callback: () => void,
  options?: StartTransitionOptions,
): void {
  const previousPriority = getCurrentUpdatePriority();
  setCurrentUpdatePriority(
    higherEventPriority(previousPriority, ContinuousEventPriority),
  );
  
  // 设置isPending为true
  dispatchSetState(fiber, queue, pendingState);
  
  const prevTransition = ReactSharedInternals.T;
  const currentTransition: BatchConfigTransition = {};
  
  ReactSharedInternals.T = currentTransition;
  
  try {
    // 设置isPending为false
    dispatchSetState(fiber, queue, finishedState);
    
    // 执行transition回调
    callback();
  } finally {
    setCurrentUpdatePriority(previousPriority);
    ReactSharedInternals.T = prevTransition;
  }
}

useTransition的工作原理

  1. 创建isPending状态:用于显示加载状态
  2. startTransition函数
    • 设置isPending为true
    • 降低更新优先级为TransitionLane
    • 执行回调函数
    • 设置isPending为false

示例:useTransition的使用

jsx
function SearchPage() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();
  
  const handleChange = (e) => {
    const value = e.target.value;
    
    // 紧急更新:立即更新输入框
    setQuery(value);
    
    // 非紧急更新:延迟更新搜索结果
    startTransition(() => {
      const filtered = searchData(value);
      setResults(filtered);
    });
  };
  
  return (
    <div>
      <input value={query} onChange={handleChange} />
      {isPending && <Spinner />}
      <Results data={results} />
    </div>
  );
}

// 执行流程:
// 1. 用户输入'a'
// 2. setQuery('a'):同步更新,优先级高
// 3. startTransition(() => setResults(...)):
//    - 设置isPending = true
//    - setResults(...):TransitionLane,优先级低
//    - 设置isPending = false
// 4. React先渲染输入框(高优先级)
// 5. 然后渲染搜索结果(低优先级,可中断)

7.6.2 useDeferredValue实现

useDeferredValue返回一个延迟版本的值,当有更紧急的更新时,会继续使用旧值。

mountDeferredValue

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js

function mountDeferredValue<T>(value: T, initialValue?: T): T {
  const hook = mountWorkInProgressHook();
  
  if (initialValue !== undefined && shouldDeferHydration()) {
    hook.memoizedState = initialValue;
    return initialValue;
  }
  
  hook.memoizedState = value;
  return value;
}

updateDeferredValue

javascript
// 文件:packages/react-reconciler/src/ReactFiberHooks.js

function updateDeferredValue<T>(value: T, initialValue?: T): T {
  const hook = updateWorkInProgressHook();
  const prevValue: T = hook.memoizedState;
  
  if (is(value, prevValue)) {
    // 值没变,返回旧值
    return value;
  }
  
  // 值变化了
  if (isCurrentTreeHidden()) {
    // 在Offscreen组件中,立即更新
    hook.memoizedState = value;
    return value;
  }
  
  // 检查是否有紧急更新
  const shouldDeferValue = !includesOnlyNonUrgentLanes(renderLanes);
  
  if (shouldDeferValue) {
    // 有紧急更新,继续使用旧值
    // 调度一个低优先级更新来更新到新值
    const deferredLane = claimNextTransitionLane();
    currentlyRenderingFiber.lanes = mergeLanes(
      currentlyRenderingFiber.lanes,
      deferredLane,
    );
    markSkippedUpdateLanes(deferredLane);
    
    return prevValue;
  } else {
    // 没有紧急更新,更新到新值
    hook.memoizedState = value;
    return value;
  }
}

useDeferredValue的工作原理

  1. 首次渲染:返回传入的值
  2. 值变化时
    • 如果有紧急更新:返回旧值,调度低优先级更新
    • 如果没有紧急更新:返回新值

示例:useDeferredValue的使用

jsx
function SearchPage() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  
  // query立即更新,deferredQuery延迟更新
  const results = useMemo(() => {
    return searchData(deferredQuery);
  }, [deferredQuery]);
  
  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      <Results data={results} />
    </div>
  );
}

// 执行流程:
// 1. 用户输入'a'
// 2. setQuery('a'):query = 'a'
// 3. deferredQuery仍然是''(旧值)
// 4. React先渲染输入框(query = 'a')
// 5. 然后更新deferredQuery = 'a'
// 6. 重新计算results

7.6.3 示例:优化搜索体验

使用useTransition

jsx
function SearchWithTransition() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();
  
  const handleChange = (e) => {
    const value = e.target.value;
    setQuery(value);
    
    startTransition(() => {
      // 模拟耗时搜索
      const filtered = hugeList.filter(item =>
        item.name.includes(value)
      );
      setResults(filtered);
    });
  };
  
  return (
    <div>
      <input value={query} onChange={handleChange} />
      {isPending ? (
        <Spinner />
      ) : (
        <Results data={results} />
      )}
    </div>
  );
}

使用useDeferredValue

jsx
function SearchWithDeferred() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  
  const results = useMemo(() => {
    // 模拟耗时搜索
    return hugeList.filter(item =>
      item.name.includes(deferredQuery)
    );
  }, [deferredQuery]);
  
  const isStale = query !== deferredQuery;
  
  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      <div style={{ opacity: isStale ? 0.5 : 1 }}>
        <Results data={results} />
      </div>
    </div>
  );
}

useTransition vs useDeferredValue

特性useTransitionuseDeferredValue
控制点控制更新函数控制值
isPending提供需要自己判断
适用场景控制setState控制props或state
灵活性更灵活更简单
jsx
// useTransition:控制更新函数
const [isPending, startTransition] = useTransition();
startTransition(() => {
  setState(newValue); // 控制这个更新
});

// useDeferredValue:控制值
const deferredValue = useDeferredValue(value); // 控制这个值

并发特性的优势

  1. 保持响应性:紧急更新不会被阻塞
  2. 更好的用户体验:输入框立即响应
  3. 自动优化:React自动处理优先级
  4. 可中断渲染:低优先级更新可以被打断
jsx
// 传统方式:输入卡顿
function Traditional() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  
  const handleChange = (e) => {
    const value = e.target.value;
    setQuery(value);
    
    // 耗时操作阻塞输入
    const filtered = hugeList.filter(item =>
      item.name.includes(value)
    );
    setResults(filtered);
  };
  
  return (
    <div>
      <input value={query} onChange={handleChange} />
      <Results data={results} />
    </div>
  );
}

// 并发方式:输入流畅
function Concurrent() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  
  const results = useMemo(() => {
    return hugeList.filter(item =>
      item.name.includes(deferredQuery)
    );
  }, [deferredQuery]);
  
  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      <Results data={results} />
    </div>
  );
}

7.7 示例:Hooks链表图解

让我们通过一个完整的示例来理解Hooks链表的构建和更新过程。

7.7.1 完整示例

jsx
function TodoApp() {
  // Hook1: useState
  const [todos, setTodos] = useState([]);
  
  // Hook2: useState
  const [filter, setFilter] = useState('all');
  
  // Hook3: useMemo
  const filteredTodos = useMemo(() => {
    if (filter === 'active') {
      return todos.filter(t => !t.done);
    }
    if (filter === 'completed') {
      return todos.filter(t => t.done);
    }
    return todos;
  }, [todos, filter]);
  
  // Hook4: useCallback
  const addTodo = useCallback((text) => {
    setTodos([...todos, { id: Date.now(), text, done: false }]);
  }, [todos]);
  
  // Hook5: useEffect
  useEffect(() => {
    localStorage.setItem('todos', JSON.stringify(todos));
  }, [todos]);
  
  // Hook6: useEffect
  useEffect(() => {
    document.title = `${filteredTodos.length} todos`;
  }, [filteredTodos]);
  
  return (
    <div>
      <TodoInput onAdd={addTodo} />
      <FilterButtons filter={filter} setFilter={setFilter} />
      <TodoList todos={filteredTodos} />
    </div>
  );
}

7.7.2 首次渲染的Hooks链表

详细结构

javascript
// Fiber节点
fiber = {
  memoizedState: Hook1,
  updateQueue: {
    lastEffect: Effect2,
    events: null,
    stores: null,
    memoCache: null,
  },
};

// Hook1: useState([])
Hook1 = {
  memoizedState: [],
  baseState: [],
  baseQueue: null,
  queue: {
    pending: null,
    dispatch: setTodos,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: [],
  },
  next: Hook2,
};

// Hook2: useState('all')
Hook2 = {
  memoizedState: 'all',
  baseState: 'all',
  baseQueue: null,
  queue: {
    pending: null,
    dispatch: setFilter,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: 'all',
  },
  next: Hook3,
};

// Hook3: useMemo
Hook3 = {
  memoizedState: [[], [[], 'all']], // [value, deps]
  baseState: null,
  baseQueue: null,
  queue: null,
  next: Hook4,
};

// Hook4: useCallback
Hook4 = {
  memoizedState: [addTodo, [[]]], // [callback, deps]
  baseState: null,
  baseQueue: null,
  queue: null,
  next: Hook5,
};

// Hook5: useEffect
Hook5 = {
  memoizedState: {
    tag: HookHasEffect | HookPassive,
    inst: { destroy: undefined },
    create: () => { localStorage.setItem(...); },
    deps: [[]],
    next: Effect2,
  },
  baseState: null,
  baseQueue: null,
  queue: null,
  next: Hook6,
};

// Hook6: useEffect
Hook6 = {
  memoizedState: {
    tag: HookHasEffect | HookPassive,
    inst: { destroy: undefined },
    create: () => { document.title = ...; },
    deps: [[]],
    next: Effect1, // 环形链表
  },
  baseState: null,
  baseQueue: null,
  queue: null,
  next: null,
};

// Effect环形链表
Effect1 -> Effect2 -> Effect1
   ↑                    ↓
   └────────────────────┘

7.7.3 更新时的Hooks链表

jsx
// 用户添加一个todo
addTodo('Learn Hooks');

// 触发更新,重新渲染TodoApp

更新流程

更新后的Hook1

javascript
// Hook1: useState(todos)
Hook1 = {
  memoizedState: [{ id: 1, text: 'Learn Hooks', done: false }],
  baseState: [{ id: 1, text: 'Learn Hooks', done: false }],
  baseQueue: null,
  queue: {
    pending: null, // 更新已处理
    dispatch: setTodos,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: [{ id: 1, text: 'Learn Hooks', done: false }],
  },
  next: Hook2,
};

更新后的Hook3

javascript
// Hook3: useMemo(filteredTodos)
Hook3 = {
  memoizedState: [
    [{ id: 1, text: 'Learn Hooks', done: false }], // 新值
    [[{ id: 1, text: 'Learn Hooks', done: false }], 'all'], // 新deps
  ],
  baseState: null,
  baseQueue: null,
  queue: null,
  next: Hook4,
};

更新后的Hook5

javascript
// Hook5: useEffect
Hook5 = {
  memoizedState: {
    tag: HookHasEffect | HookPassive, // 标记需要执行
    inst: { destroy: undefined },
    create: () => { localStorage.setItem(...); },
    deps: [[{ id: 1, text: 'Learn Hooks', done: false }]], // 新deps
    next: Effect2,
  },
  baseState: null,
  baseQueue: null,
  queue: null,
  next: Hook6,
};

7.7.4 Hooks调用顺序的重要性

错误示例:条件调用Hooks

jsx
function BadComponent({ condition }) {
  if (condition) {
    const [state, setState] = useState(0); // ✗ 错误
  }
  
  const [count, setCount] = useState(0);
  
  return <div>{count}</div>;
}

// 首次渲染(condition = true):
// Hook1: useState(state)
// Hook2: useState(count)

// 第二次渲染(condition = false):
// Hook1: useState(count) ← 错误!匹配到了state的Hook

// 结果:状态错乱

正确示例:顶层调用Hooks

jsx
function GoodComponent({ condition }) {
  const [state, setState] = useState(0); // ✓ 正确
  const [count, setCount] = useState(0);
  
  if (condition) {
    // 使用state
  }
  
  return <div>{count}</div>;
}

// 每次渲染:
// Hook1: useState(state)
// Hook2: useState(count)

// 结果:状态正确

React如何检测Hooks顺序错误

javascript
// 开发环境下,React会记录Hooks的类型
let hookTypesDev = null;

function mountHookTypesDev() {
  const hookName = currentHookNameInDev;
  if (hookTypesDev === null) {
    hookTypesDev = [hookName];
  } else {
    hookTypesDev.push(hookName);
  }
}

function updateHookTypesDev() {
  const hookName = currentHookNameInDev;
  if (hookTypesDev !== null) {
    const expectedHookName = hookTypesDev[hookTypesUpdateIndexDev];
    if (hookName !== expectedHookName) {
      // 检测到Hooks顺序错误
      warnOnHookMismatchInDev(expectedHookName, hookName);
    }
    hookTypesUpdateIndexDev++;
  }
}

本章小结

本章深入讲解了React Hooks的实现原理,让我们回顾一下关键要点:

Hooks的数据结构

  1. Hook对象:保存Hook的状态和更新队列
  2. Effect对象:保存副作用函数和依赖
  3. Hooks链表:通过next指针连接,保证调用顺序

Dispatcher机制

  1. ContextOnlyDispatcher:非渲染阶段,抛出错误
  2. HooksDispatcherOnMount:首次渲染,创建Hook
  3. HooksDispatcherOnUpdate:更新时,复用Hook

核心Hooks实现

  1. useState:基于useReducer实现,支持eager state优化
  2. useEffect:异步执行,支持依赖比较和cleanup
  3. useMemo/useCallback:缓存值和函数,避免不必要的计算
  4. useTransition/useDeferredValue:并发特性,优化用户体验

关键原则

  1. Hooks必须在顶层调用:保证调用顺序一致
  2. 依赖数组很重要:决定是否重新执行
  3. 引用相等性:使用Object.is比较依赖
  4. 性能优化:合理使用useMemo和useCallback

思考题

  1. 为什么Hooks必须在函数组件顶层调用?
  2. useState的eager state优化在什么情况下生效?
  3. useEffect和useLayoutEffect的执行时机有什么区别?
  4. useTransition和useDeferredValue应该如何选择?

在下一章中,我们将进入服务端篇,学习React 19的核心特性:流式SSR(Fizz)。