Skip to content

Hooks链表结构

学习目标

通过本章学习,你将深入理解:

  • React Hooks的底层数据结构
  • Fiber节点与Hooks的关系
  • Hooks链表的工作原理
  • 为什么Hooks必须在顶层调用
  • Hooks的初始化和更新流程
  • 多个Hooks如何组织和管理
  • Hooks链表的遍历机制
  • React 19中的优化

第一部分:Hooks数据结构基础

1.1 Hook对象的结构

每个Hook在React内部都表示为一个Hook对象:

javascript
// Hook对象的基本结构
type Hook = {
  memoizedState: any,        // 存储Hook的状态值
  baseState: any,            // 基础状态
  baseQueue: Update<any> | null,  // 基础更新队列
  queue: UpdateQueue<any> | null,  // 更新队列
  next: Hook | null          // 指向下一个Hook
};

各字段含义:

javascript
// 1. memoizedState - 存储不同类型Hook的状态
useState: {
  memoizedState: state值
}

useEffect: {
  memoizedState: {
    create: effect函数,
    destroy: cleanup函数,
    deps: 依赖数组,
    next: 下一个effect,
    tag: effect标记
  }
}

useMemo: {
  memoizedState: [缓存的值, 依赖数组]
}

useRef: {
  memoizedState: { current: 引用的值 }
}

// 2. baseState - 基础状态(用于状态更新)
// 3. baseQueue - 基础更新队列
// 4. queue - 当前更新队列
// 5. next - 链表的下一个节点

1.2 Fiber节点与Hooks

Hooks挂载在Fiber节点上:

javascript
// Fiber节点简化结构
type Fiber = {
  // ...其他属性
  
  // 函数组件的Hooks链表
  memoizedState: Hook | null,  // 指向第一个Hook
  
  // 更新队列
  updateQueue: UpdateQueue | null,
  
  // 组件类型和函数
  type: Function,
  stateNode: any
};

// 示例:组件与Fiber的关系
function Component() {
  const [count, setCount] = useState(0);     // Hook 1
  const [name, setName] = useState('');      // Hook 2
  useEffect(() => {}, []);                   // Hook 3
  
  return <div>{count} - {name}</div>;
}

// 对应的Fiber结构:
// Fiber.memoizedState → Hook1 → Hook2 → Hook3 → null
//                       (count)  (name)  (effect)

1.3 链表结构可视化

Fiber节点
  |
  | memoizedState

Hook1 (useState)
  | next

Hook2 (useState)
  | next

Hook3 (useEffect)
  | next

Hook4 (useMemo)
  | next

null

完整示例:

javascript
function UserProfile() {
  // Hook 1
  const [user, setUser] = useState(null);
  
  // Hook 2
  const [loading, setLoading] = useState(true);
  
  // Hook 3
  useEffect(() => {
    fetchUser().then(setUser);
  }, []);
  
  // Hook 4
  const displayName = useMemo(() => {
    return user?.name || '匿名';
  }, [user]);
  
  // Hook 5
  const handleUpdate = useCallback(() => {
    updateUser(user);
  }, [user]);
  
  return <div>{displayName}</div>;
}

// Fiber.memoizedState链表:
// Hook1(user) → Hook2(loading) → Hook3(effect) → Hook4(displayName) → Hook5(handleUpdate) → null

第二部分:Hooks初始化流程

2.1 首次渲染(Mount阶段)

初始化过程:

javascript
// 简化的mountState实现
function mountState(initialState) {
  // 1. 创建新的Hook对象
  const hook = {
    memoizedState: initialState,
    baseState: initialState,
    baseQueue: null,
    queue: {
      pending: null,
      dispatch: null,
      lastRenderedReducer: basicStateReducer,
      lastRenderedState: initialState
    },
    next: null
  };
  
  // 2. 将Hook添加到Fiber的链表中
  if (workInProgressHook === null) {
    // 第一个Hook
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    // 后续Hook,添加到链表末尾
    workInProgressHook = workInProgressHook.next = hook;
  }
  
  // 3. 创建dispatch函数
  const dispatch = dispatchSetState.bind(
    null,
    currentlyRenderingFiber,
    hook.queue
  );
  hook.queue.dispatch = dispatch;
  
  // 4. 返回状态和dispatch
  return [hook.memoizedState, dispatch];
}

// 使用示例
function Component() {
  // 第一次调用useState
  const [count, setCount] = useState(0);
  // → 创建Hook1,挂载到Fiber.memoizedState
  
  // 第二次调用useState
  const [name, setName] = useState('');
  // → 创建Hook2,Hook1.next = Hook2
  
  return <div>{count} - {name}</div>;
}

初始化流程图:

1. 调用useState(0)

2. mountState被调用

3. 创建Hook对象
   {
     memoizedState: 0,
     next: null,
     queue: { ... }
   }

4. 添加到Fiber链表
   Fiber.memoizedState = Hook1

5. 返回 [0, setState]

再次调用useState('')

mountState被调用

创建Hook对象
   {
     memoizedState: '',
     next: null,
     queue: { ... }
   }

添加到链表
   Hook1.next = Hook2

返回 ['', setState]

2.2 更新渲染(Update阶段)

javascript
// 简化的updateState实现
function updateState(initialState) {
  // 1. 获取当前Hook(从链表中)
  const hook = updateWorkInProgressHook();
  
  // 2. 处理更新队列
  const queue = hook.queue;
  const pending = queue.pending;
  
  if (pending !== null) {
    // 3. 计算新状态
    let newState = hook.memoizedState;
    let update = pending.next;
    
    do {
      // 应用每个更新
      const action = update.action;
      newState = typeof action === 'function' 
        ? action(newState) 
        : action;
      update = update.next;
    } while (update !== pending.next);
    
    // 4. 更新Hook状态
    hook.memoizedState = newState;
    hook.baseState = newState;
    queue.pending = null;
  }
  
  // 5. 返回状态和dispatch
  const dispatch = queue.dispatch;
  return [hook.memoizedState, dispatch];
}

// updateWorkInProgressHook - 获取下一个Hook
function updateWorkInProgressHook() {
  // 从current树获取对应的Hook
  let nextCurrentHook;
  if (currentHook === null) {
    // 第一个Hook
    const current = currentlyRenderingFiber.alternate;
    nextCurrentHook = current.memoizedState;
  } else {
    // 后续Hook,沿着链表向下
    nextCurrentHook = currentHook.next;
  }
  
  currentHook = nextCurrentHook;
  
  // 创建workInProgress Hook
  const newHook = {
    memoizedState: currentHook.memoizedState,
    baseState: currentHook.baseState,
    baseQueue: currentHook.baseQueue,
    queue: currentHook.queue,
    next: null
  };
  
  // 添加到workInProgress链表
  if (workInProgressHook === null) {
    workInProgressHook = newHook;
    currentlyRenderingFiber.memoizedState = newHook;
  } else {
    workInProgressHook = workInProgressHook.next = newHook;
  }
  
  return workInProgressHook;
}

更新流程图:

组件重新渲染

1. 调用useState()

2. updateState被调用

3. 从Fiber链表获取对应Hook
   currentHook = Fiber.alternate.memoizedState (第一个Hook)

4. 处理pending更新
   应用所有update

5. 返回新状态
   [newState, dispatch]

6. 继续调用下一个useState()

7. 获取下一个Hook
   currentHook = currentHook.next

8. 重复步骤4-5

第三部分:为什么Hooks必须在顶层调用

3.1 规则的本质原因

Hooks依赖于调用顺序来维护状态对应关系。

错误示例:

javascript
// ❌ 错误:条件调用Hook
function BadComponent({ condition }) {
  if (condition) {
    const [state1, setState1] = useState(0);  // 有时调用
  }
  const [state2, setState2] = useState('');   // 总是调用
  
  return <div>{state2}</div>;
}

// 问题分析:
// 首次渲染(condition = true):
// Hook链表: Hook1(state1) → Hook2(state2) → null

// 第二次渲染(condition = false):
// Hook链表: Hook1(state2) → null
// ❌ state2对应到了Hook1的位置!状态错乱!

正确示例:

javascript
// ✅ 正确:始终调用Hook
function GoodComponent({ condition }) {
  const [state1, setState1] = useState(0);
  const [state2, setState2] = useState('');
  
  // 根据条件使用状态
  const displayState = condition ? state1 : state2;
  
  return <div>{displayState}</div>;
}

// Hook链表始终一致:
// 任何渲染: Hook1(state1) → Hook2(state2) → null

3.2 链表顺序的重要性

javascript
// 示例:多个Hooks的顺序
function Component() {
  const [count, setCount] = useState(0);        // 位置 0
  const [name, setName] = useState('');         // 位置 1
  useEffect(() => {}, []);                      // 位置 2
  const value = useMemo(() => count * 2, [count]); // 位置 3
  
  // Hook链表:
  // 0: Hook(useState, count)
  // 1: Hook(useState, name)
  // 2: Hook(useEffect)
  // 3: Hook(useMemo, value)
  
  return <div>{count} - {name} - {value}</div>;
}

// 更新时React会:
// 1. 从位置0获取Hook → count
// 2. 从位置1获取Hook → name
// 3. 从位置2获取Hook → effect
// 4. 从位置3获取Hook → value

// 如果顺序改变,状态就会错乱!

3.3 循环和条件的问题

javascript
// ❌ 错误:循环中调用Hook
function BadLoop({ items }) {
  items.forEach(item => {
    const [state, setState] = useState(item.value); // 错误!
  });
}

// 问题:
// items = [1, 2, 3]时:Hook1 → Hook2 → Hook3
// items = [1, 2]时:Hook1 → Hook2
// Hook数量不一致导致错误!

// ✅ 正确:固定数量的Hook
function GoodLoop({ items }) {
  const [states, setStates] = useState(
    items.map(item => item.value)
  );
  
  return states.map((state, index) => (
    <div key={index}>{state}</div>
  ));
}

第四部分:不同Hook的链表节点

4.1 useState的Hook结构

javascript
// useState Hook
{
  memoizedState: currentValue,  // 当前状态值
  baseState: baseValue,         // 基础状态
  baseQueue: null,              // 基础队列
  queue: {                      // 更新队列
    pending: null,              // 待处理更新
    dispatch: setStateFunction, // setState函数
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: currentValue
  },
  next: nextHook                // 下一个Hook
}

// 示例
function Component() {
  const [count, setCount] = useState(0);
  
  // 对应的Hook:
  // {
  //   memoizedState: 0,
  //   queue: { dispatch: setCount, ... },
  //   next: ...
  // }
}

4.2 useEffect的Hook结构

javascript
// useEffect Hook
{
  memoizedState: {
    tag: effectTag,          // effect标记(HookHasEffect等)
    create: effectFunction,  // effect函数
    destroy: cleanupFunction, // cleanup函数
    deps: dependencyArray,   // 依赖数组
    next: nextEffect         // 下一个effect(effect链表)
  },
  baseState: null,
  baseQueue: null,
  queue: null,
  next: nextHook             // 下一个Hook
}

// 示例
function Component() {
  useEffect(() => {
    // effect函数
    console.log('effect');
    
    return () => {
      // cleanup函数
      console.log('cleanup');
    };
  }, [dependency]);
  
  // 对应的Hook:
  // {
  //   memoizedState: {
  //     create: effectFunction,
  //     destroy: cleanupFunction,
  //     deps: [dependency],
  //     ...
  //   }
  // }
}

4.3 useMemo和useCallback的Hook结构

javascript
// useMemo/useCallback Hook
{
  memoizedState: [value, deps],  // [缓存的值, 依赖数组]
  baseState: null,
  baseQueue: null,
  queue: null,
  next: nextHook
}

// useMemo示例
function Component() {
  const expensive = useMemo(() => {
    return heavyComputation();
  }, [dependency]);
  
  // Hook结构:
  // {
  //   memoizedState: [computedValue, [dependency]],
  //   next: ...
  // }
}

// useCallback示例(本质是useMemo的特殊形式)
function Component() {
  const callback = useCallback(() => {
    doSomething();
  }, [dependency]);
  
  // Hook结构:
  // {
  //   memoizedState: [callbackFunction, [dependency]],
  //   next: ...
  // }
}

4.4 useRef的Hook结构

javascript
// useRef Hook
{
  memoizedState: { current: value },  // ref对象
  baseState: null,
  baseQueue: null,
  queue: null,
  next: nextHook
}

// 示例
function Component() {
  const ref = useRef(initialValue);
  
  // Hook结构:
  // {
  //   memoizedState: { current: initialValue },
  //   next: ...
  // }
  
  // 更新ref.current不会创建新Hook
  ref.current = newValue;  // 直接修改memoizedState.current
}

第五部分:Hooks链表的遍历

5.1 初始化遍历

javascript
// 首次渲染时构建链表
let currentlyRenderingFiber = null;
let workInProgressHook = null;

function renderWithHooks(fiber) {
  currentlyRenderingFiber = fiber;
  fiber.memoizedState = null;  // 重置链表
  workInProgressHook = null;
  
  // 执行组件函数
  const Component = fiber.type;
  const props = fiber.pendingProps;
  const children = Component(props);  // 这里会调用所有Hooks
  
  return children;
}

// 组件执行时
function Component() {
  // 每次调用Hook都会创建节点并添加到链表
  const [state1] = useState(0);   // Hook1
  const [state2] = useState('');  // Hook2
  useEffect(() => {});            // Hook3
  
  // 最终链表:Hook1 → Hook2 → Hook3 → null
}

5.2 更新时的遍历

javascript
// 更新渲染时遍历链表
let currentHook = null;         // current树的Hook
let workInProgressHook = null;  // workInProgress树的Hook

function renderWithHooks(fiber) {
  const current = fiber.alternate;
  
  // 设置current Hook链表的起点
  if (current !== null) {
    currentHook = current.memoizedState;
  }
  
  currentlyRenderingFiber = fiber;
  fiber.memoizedState = null;
  workInProgressHook = null;
  
  // 执行组件
  const children = Component(props);
  
  return children;
}

// 每次调用Hook时
function updateWorkInProgressHook() {
  // 1. 从current树获取Hook
  const nextCurrentHook = currentHook !== null 
    ? currentHook.next 
    : null;
  
  // 2. 移动current指针
  currentHook = nextCurrentHook;
  
  // 3. 创建workInProgress Hook
  const newHook = {
    memoizedState: nextCurrentHook.memoizedState,
    // ...
    next: null
  };
  
  // 4. 添加到workInProgress链表
  if (workInProgressHook === null) {
    workInProgressHook = newHook;
    fiber.memoizedState = newHook;
  } else {
    workInProgressHook.next = newHook;
    workInProgressHook = newHook;
  }
  
  return workInProgressHook;
}

5.3 链表遍历的完整流程

javascript
// 完整示例
function Component() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');
  useEffect(() => {});
  
  return <div>{count} - {name}</div>;
}

// 初始渲染:
// 1. 调用useState(0)
//    → mountState → 创建Hook1 → Fiber.memoizedState = Hook1
// 2. 调用useState('')
//    → mountState → 创建Hook2 → Hook1.next = Hook2
// 3. 调用useEffect
//    → mountEffect → 创建Hook3 → Hook2.next = Hook3
// 最终:Hook1 → Hook2 → Hook3 → null

// 更新渲染:
// 1. 设置currentHook = Fiber.alternate.memoizedState (Hook1)
// 2. 调用useState(0)
//    → updateState → 从currentHook获取 → 创建新Hook1
//    → currentHook = Hook1.next (Hook2)
// 3. 调用useState('')
//    → updateState → 从currentHook获取 → 创建新Hook2
//    → currentHook = Hook2.next (Hook3)
// 4. 调用useEffect
//    → updateEffect → 从currentHook获取 → 创建新Hook3
//    → currentHook = Hook3.next (null)

第六部分:React 19的优化

6.1 Hooks优化

React 19对Hooks链表进行了多项优化:

javascript
// 1. 更高效的Hook查找
// React 18及之前:线性遍历链表
function findHook(index) {
  let hook = fiber.memoizedState;
  for (let i = 0; i < index; i++) {
    hook = hook.next;
  }
  return hook;
}

// React 19:使用索引优化
type Fiber = {
  memoizedState: Hook | null,
  hookIndex: number,  // 新增:当前Hook索引
  hookMap: Map<number, Hook>  // 新增:Hook索引映射
}

// 2. 批量更新优化
// 多个setState调用会被合并
function Component() {
  const [state1, setState1] = useState(0);
  const [state2, setState2] = useState(0);
  
  const handleClick = () => {
    setState1(1);  // 这些更新会被批量处理
    setState2(2);
    setState1(3);
  };
}

6.2 编译器优化

javascript
// React 19编译器可以优化Hook调用

// 原始代码
function Component({ items }) {
  const [selected, setSelected] = useState(null);
  
  const filtered = items.filter(item => {
    return item.id === selected;
  });
  
  return <List items={filtered} />;
}

// 编译器自动优化为
function Component({ items }) {
  const [selected, setSelected] = useState(null);
  
  // 自动添加useMemo
  const filtered = useMemo(() => {
    return items.filter(item => {
      return item.id === selected;
    });
  }, [items, selected]);
  
  return <List items={filtered} />;
}

注意事项

1. Hook调用顺序不能改变

javascript
// ❌ 错误
function Component({ showExtra }) {
  const [count, setCount] = useState(0);
  
  if (showExtra) {
    const [extra, setExtra] = useState('');  // 条件Hook!
  }
  
  return <div>{count}</div>;
}

// ✅ 正确
function Component({ showExtra }) {
  const [count, setCount] = useState(0);
  const [extra, setExtra] = useState('');  // 始终调用
  
  return (
    <div>
      {count}
      {showExtra && extra}
    </div>
  );
}

2. 不能在循环中调用Hook

javascript
// ❌ 错误
function Component({ items }) {
  return items.map(item => {
    const [state] = useState(item);  // 循环中的Hook!
    return <div>{state}</div>;
  });
}

// ✅ 正确
function Component({ items }) {
  const [states] = useState(items);
  
  return states.map((state, index) => (
    <div key={index}>{state}</div>
  ));
}

3. 只在React函数中调用Hook

javascript
// ❌ 错误
function helper() {
  const [state] = useState(0);  // 在普通函数中!
  return state;
}

// ✅ 正确
function useHelper() {  // 自定义Hook
  const [state] = useState(0);
  return state;
}

常见问题

Q1: 为什么不能动态改变Hook数量?

A: 因为Hook依赖位置索引:

javascript
// Hook通过位置对应状态
// 位置0 → count
// 位置1 → name  
// 位置2 → effect

// 如果动态改变:
// 第一次:位置0=count, 位置1=name, 位置2=effect
// 第二次:位置0=count, 位置1=effect(name被跳过)
// ❌ 位置1现在是effect,但React认为它是name!

Q2: 链表结构会不会影响性能?

A: 影响很小:

javascript
// 1. Hook数量通常不多(<20个)
// 2. 链表遍历是O(n),但n很小
// 3. React有优化:
//    - 首次渲染缓存Hook
//    - 更新时复用Hook对象
//    - React 19的索引优化

Q3: 为什么使用链表而不是数组?

A: 链表的优势:

javascript
// 1. 动态构建
//    - 不需要预知Hook数量
//    - 按需创建节点

// 2. 内存效率
//    - 只分配需要的节点
//    - 数组可能预分配过多空间

// 3. 简化逻辑
//    - 添加节点只需修改next指针
//    - 数组需要索引管理

// 4. 与Fiber架构契合
//    - Fiber本身就是链表结构
//    - Hook链表是Fiber链表的一部分

总结

核心概念

  1. Hook对象:包含memoizedState、queue、next等字段
  2. 链表结构:Hook通过next指针连接成链表
  3. Fiber关联:Hook链表挂载在Fiber.memoizedState上
  4. 顺序依赖:Hook依赖调用顺序维护状态对应关系
  5. 双缓冲:current树和workInProgress树各有Hook链表

Hook规则的本质

规则:
1. 只在顶层调用Hook
2. 只在React函数中调用Hook

原因:

Hook依赖链表位置索引

顺序改变导致状态错乱

必须保证调用顺序一致

链表遍历流程

初始渲染:
构建链表 → Hook1 → Hook2 → Hook3 → null

更新渲染:
遍历current链表 → 创建workInProgress链表 → 复制并更新Hook

React 19优化

  • Hook索引映射
  • 批量更新优化
  • 编译器自动优化
  • 更高效的遍历算法

理解Hooks链表结构是深入掌握React的关键,它解释了Hook规则的本质原因,也为我们编写更好的React代码提供了理论基础!