React Hooks 原理与规则
trycoding.fun

React Hooks 原理与规则

理解 Hooks 的链表存储机制和调用规则

问题

React Hooks 是如何存储状态的?为什么不能在条件语句中调用 Hooks?

解答

Hooks 的存储结构

React 使用链表来存储组件的 Hooks 状态。每个组件对应一个 Fiber 节点,Fiber 上有一个 memoizedState 属性指向 Hooks 链表的头节点。

// 简化的 Hook 节点结构
const hook = {
  memoizedState: null, // 存储的状态值
  next: null           // 指向下一个 Hook
};

// Fiber 节点
const fiber = {
  memoizedState: hook1, // 指向第一个 Hook
  // ...其他属性
};

// 链表结构示意
// fiber.memoizedState -> hook1 -> hook2 -> hook3 -> null

简化实现

// 模拟 React Hooks 的简化实现
let currentFiber = null;
let workInProgressHook = null;

// 模拟 Fiber 节点
function createFiber() {
  return {
    memoizedState: null, // Hooks 链表头
    stateNode: null
  };
}

// useState 简化实现
function useState(initialValue) {
  let hook;
  
  if (workInProgressHook === null) {
    // 第一个 Hook
    hook = {
      memoizedState: initialValue,
      next: null
    };
    currentFiber.memoizedState = hook;
    workInProgressHook = hook;
  } else {
    // 后续 Hook,挂到链表末尾
    hook = {
      memoizedState: initialValue,
      next: null
    };
    workInProgressHook.next = hook;
    workInProgressHook = hook;
  }
  
  const setState = (newValue) => {
    hook.memoizedState = newValue;
    // 触发重新渲染...
  };
  
  return [hook.memoizedState, setState];
}

// 模拟组件渲染
function renderComponent(Component) {
  currentFiber = createFiber();
  workInProgressHook = null;
  
  // 执行组件函数
  Component();
}

更新时的 Hook 读取

// 更新阶段的 useState
let isMount = true; // 是否首次渲染
let currentHook = null;

function useState(initialValue) {
  let hook;
  
  if (isMount) {
    // 首次渲染:创建新 Hook
    hook = {
      memoizedState: initialValue,
      next: null
    };
    if (!currentFiber.memoizedState) {
      currentFiber.memoizedState = hook;
    } else {
      workInProgressHook.next = hook;
    }
    workInProgressHook = hook;
  } else {
    // 更新渲染:从链表中按顺序读取
    hook = currentHook;
    currentHook = currentHook.next;
  }
  
  const setState = (newValue) => {
    hook.memoizedState = newValue;
    // 重新渲染时,currentHook 重置为链表头
    currentHook = currentFiber.memoizedState;
    isMount = false;
    renderComponent(Component);
  };
  
  return [hook.memoizedState, setState];
}

为什么不能在条件语句中调用

// ❌ 错误示例
function BadComponent({ showName }) {
  const [count, setCount] = useState(0);
  
  // 条件调用 Hook
  if (showName) {
    const [name, setName] = useState('React');
  }
  
  const [age, setAge] = useState(18);
  
  return <div>{count}</div>;
}

// 首次渲染 (showName = true)
// 链表: hook1(count) -> hook2(name) -> hook3(age)
//       位置1          位置2          位置3

// 第二次渲染 (showName = false)
// 期望: hook1(count) -> hook3(age)
// 实际读取: 位置1 -> 位置2 -> 位置3
// 结果: age 读到了 name 的值!
// ✅ 正确做法
function GoodComponent({ showName }) {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('React');
  const [age, setAge] = useState(18);
  
  // 条件放在 Hook 外部
  const displayName = showName ? name : '';
  
  return <div>{count}</div>;
}

// 每次渲染 Hook 调用顺序一致
// 链表: hook1(count) -> hook2(name) -> hook3(age)

完整示例

// 模拟完整的 Hooks 系统
const React = (function() {
  let fiber = { memoizedState: null };
  let isMount = true;
  let workInProgressHook = null;
  
  function useState(initialValue) {
    let hook;
    
    if (isMount) {
      hook = { memoizedState: initialValue, next: null };
      if (!fiber.memoizedState) {
        fiber.memoizedState = hook;
      } else {
        workInProgressHook.next = hook;
      }
      workInProgressHook = hook;
    } else {
      hook = workInProgressHook;
      workInProgressHook = workInProgressHook.next;
    }
    
    const setState = (action) => {
      hook.memoizedState = 
        typeof action === 'function' 
          ? action(hook.memoizedState) 
          : action;
    };
    
    return [hook.memoizedState, setState];
  }
  
  function render(Component) {
    workInProgressHook = isMount ? null : fiber.memoizedState;
    const result = Component();
    isMount = false;
    return result;
  }
  
  return { useState, render };
})();

// 测试
function Counter() {
  const [count, setCount] = React.useState(0);
  const [text, setText] = React.useState('hello');
  
  console.log('count:', count, 'text:', text);
  
  return { count, setCount, text, setText };
}

const result1 = React.render(Counter); // count: 0, text: hello
result1.setCount(1);
result1.setText('world');
React.render(Counter); // count: 1, text: world

关键点

  • 链表存储:Hooks 以链表形式存储在 Fiber 节点的 memoizedState
  • 顺序依赖:更新时按调用顺序从链表中读取对应的 Hook
  • 条件调用问题:条件语句会改变 Hook 调用顺序,导致状态错位
  • 规则本质:保证每次渲染 Hook 数量和顺序一致,才能正确匹配状态
  • ESLint 插件:使用 eslint-plugin-react-hooks 自动检测违规调用