【React源码04】深入学习React 源码实现—— React Hooks 的底层实现原理(useState,useEffect,useRef)

深入学习 React Hooks 的底层实现原理


一、历史背景与设计动机

1.1 函数组件的局限性(React 16.7 之前)

在 React 16.8 引入 Hooks 之前,函数组件:

  • 无法管理状态(只能使用 props);
  • 无法执行副作用(如数据获取、DOM 操作);
  • 难以复用逻辑(需要高阶组件或 render props);

这导致开发者更倾向于使用类组件,尽管它们语法复杂、this 上下文混乱。

1.2 React Hooks 的诞生(React 16.8)

Facebook 团队于 2019 年正式发布 React 16.8,引入了 Hooks API

  • useState:让函数组件拥有状态;
  • useEffect:处理副作用;
  • useContextuseReduceruseCallback 等辅助 Hook;
  • 支持自定义 Hook,逻辑复用变得简单;
  • 所有功能都基于 Fiber 架构和链表结构实现。
  • 记忆方法/命名约定:use+XXX

⚽老曹开发心得:react18入门简单,只要学会useState,useEffect,useRef三个万精油就可以解决大部分业务了,剩下hooks都是性能优化方面的

二、核心源码路径与模块说明

React Hooks 的实现主要位于以下源码文件中:

文件路径 功能描述
packages/react-reconciler/src/ReactFiberHooks.js Hooks 的核心实现,包括 useState、useEffect 等
packages/react-reconciler/src/ReactFiberHooks.old.js 兼容旧版 Fiber 实现
packages/react-reconciler/src/ReactUpdateQueue.js 更新队列的创建与处理,支持 useEffect 的调度
packages/react/src/ReactHooks.js 对外暴露的 Hook API 接口

三、Hooks 的链表结构与存储机制

3.1 Hook 对象结构(简化版)

function Hook() {
  this.memoizedState = null;     // 存储当前 Hook 的状态(如 state、effect)
  this.baseState = null;         // 基础状态(用于更新合并)
  this.baseQueue = null;         // 基础更新队列
  this.queue = null;             // 当前 Hook 的更新队列
  this.next = null;              // 指向下一个 Hook 节点(形成链表)
}

3.2 Hooks 是如何存储的?

  • 每个函数组件实例对应一个 fiber.memoizedState,它是一个 Hook 链表头节点
  • 组件首次渲染时,每个 Hook 被依次创建并串联成链表;
  • 后续更新时,按顺序复用这些 Hook 节点;
  • 调用顺序必须一致,否则会导致状态错乱(⚠️ 这是 React 官方强制要求的规则);

3.3 Hook 链表结构图示

fiber.memoizedState
       ↓
Hook A (useState)
       ↓
Hook B (useEffect)
       ↓
Hook C (useRef)
       ↓
null

四、useState 的内部实现详解

4.1 使用方式

const [count, setCount] = useState(0);

4.2 内部流程(简化)

4.2.1初始化阶段:
function mountState(initialState) {
  const hook = mountWorkInProgressHook(); // 创建新 Hook
  hook.memoizedState = initialState;
  const queue = { pending: null };        // 创建更新队列
  hook.queue = queue;

  const dispatch = dispatchAction.bind(null, queue); // 创建 dispatch 函数
  return [hook.memoizedState, dispatch];
}

算法详解

✅ 一、初始化阶段:mountState

function mountState(initialState) {
  • 作用:在组件首次渲染时创建一个状态 Hook。
  • 参数说明
    • initialState:用户传入的初始状态值(如 0''{} 等)。
  const hook = mountWorkInProgressHook();
  • 作用:从当前 Fiber 节点中获取一个新的 Hook 对象。
  • 解释:React 使用链表结构保存每个组件中的所有 Hook。这个函数会创建一个新的 Hook 并插入到链表中。
  hook.memoizedState = initialState;
  • 作用:将用户传入的初始状态保存到 Hook 的 memoizedState 字段中。
  • 说明:这是当前 Hook 的当前状态值。
  const queue = { pending: null };
  • 作用:创建一个更新队列对象,用于管理后续的状态更新操作。
  • 字段说明
    • pending: 指向当前未处理的更新操作(环形链表结构)。
  hook.queue = queue;
  • 作用:将更新队列挂载到当前 Hook 上,便于后续更新时使用。
  const dispatch = dispatchAction.bind(null, queue);
  • 作用:创建一个 dispatch 函数,供用户调用 setCount 时触发状态更新。
  • 绑定参数queue 是当前 Hook 的更新队列。
  return [hook.memoizedState, dispatch];
  • 作用:返回一个数组 [state, setState],供用户解构使用。
  • 说明
    • hook.memoizedState:当前状态;
    • dispatch:状态更新函数(即 setCount);

4.2.2更新阶段:
function updateState() {
  const hook = updateWorkInProgressHook();
  const queue = hook.queue;
  const baseQueue = hook.baseQueue;
  const pendingQueue = queue.pending;

  // 合并更新队列
  if (pendingQueue !== null) {
    hook.baseQueue = baseQueue.next = pendingQueue;
    queue.pending = null;
  }

  const newState = processUpdateQueue(baseQueue, hook.memoizedState);
  hook.memoizedState = newState;
  return [newState, queue.dispatch];
}

算法详解

✅ 二、更新阶段:updateState

function updateState() {
  • 作用:在组件后续更新时复用已有的 Hook,并计算新的状态值。
  const hook = updateWorkInProgressHook();
  • 作用:从当前 Fiber 中获取对应的 Hook 对象(按顺序匹配)。
  • 注意:必须保持 Hook 的调用顺序一致,否则会导致状态错乱!
  const queue = hook.queue;
  • 作用:获取该 Hook 的更新队列。
  const baseQueue = hook.baseQueue;
  • 作用:获取基础更新队列(用于合并新旧更新)。
  const pendingQueue = queue.pending;
  • 作用:获取当前待处理的更新队列(由用户调用 setState 触发)。
  if (pendingQueue !== null) {
    hook.baseQueue = baseQueue.next = pendingQueue;
    queue.pending = null;
  }
  • 作用:将待处理的更新加入到基础队列中。
  • 说明:React 使用的是一个环形链表结构来管理更新,这里将 pending 更新拼接到 base 队列末尾,并清空 pending。
  const newState = processUpdateQueue(baseQueue, hook.memoizedState);
  • 作用:根据更新队列和当前状态,计算出最新的状态值。
  • 关键函数processUpdateQueue 会遍历整个更新链表,依次应用每个更新动作(如 setCount(count + 1))。
  hook.memoizedState = newState;
  • 作用:将计算后的最新状态保存回 Hook 中。
  return [newState, queue.dispatch];
  • 作用:返回 [state, setState],供用户使用。

三、useState 的算法设计思路与步骤总结

1. 核心目标

为函数组件提供一种声明式管理状态的能力,支持:

  • 初始状态设置;
  • 状态更新;
  • 异步更新;
  • 支持多次调用多个 useState
  • 支持依赖更新(如 useEffect 监听 state 变化);

2. 设计思想

设计思想 实现方式
链表结构 每个 Hook 是链表节点,通过 next 指针连接
双缓冲机制 current 和 workInProgress Hook 分离
更新队列 使用环形链表保存所有状态更新
异步调度 使用 React 的任务调度器控制执行时机
不可变性 每次更新生成新状态,避免直接修改原状态

3. 实现步骤详解

步骤 1:创建 Hook 链表
  • 组件首次渲染时,每个 useState 调用都会创建一个 Hook;
  • 所有 Hook 通过 next 指针串联成链表;
  • 每个 Hook 保存自己的状态、更新队列等信息。
步骤 2:初始化状态
  • 将用户传入的 initialState 存入 hook.memoizedState
  • 创建更新队列 queue,并将其绑定到 Hook 上;
  • 返回 [state, setState],供用户使用。
步骤 3:触发更新
  • 用户调用 setState(newValue) → 调用 dispatchAction(queue, action)
  • action 表示一次状态更新操作(可能是函数或值);
  • 更新被放入 queue.pending 队列中,等待下一轮处理。
步骤 4:处理更新队列
  • 在组件重新渲染时,进入 updateState
  • 获取 Hook 的更新队列;
  • 合并 pending 队列到 base 队列;
  • 遍历队列,依次执行每个 action,计算出最终的新状态;
  • 更新 Hook 的 memoizedState。
步骤 5:返回新状态
  • 最终返回 [newState, dispatch],供用户使用;
  • 如果状态变化,触发组件重新渲染;
  • 如果未变化,则跳过渲染。

四、相关函数关系图(简化)

useState(initialState)
       ↓
mountState / updateState
       ↓
mountWorkInProgressHook / updateWorkInProgressHook
       ↓
创建/复用 Hook 链表节点
       ↓
dispatchAction → 加入 queue.pending
       ↓
processUpdateQueue → 应用所有 action 得到 newState
       ↓
返回 [newState, dispatch]

五、useEffect 的副作用处理机制

以下是对 useEffect 内部实现代码的逐行详细注释,以及对其算法设计思想与执行流程步骤的完整总结。


✅ 一、使用方式回顾

useEffect(() => {
  console.log('mounted or updated');
  return () => {
    console.log('cleanup');
  };
}, [deps]);
  • create: 用户传入的副作用函数;
  • return () => {}: 可选的清理函数;
  • [deps]: 依赖项数组,决定是否重新执行 effect;
  • 省略 deps 表示每次组件更新都执行;
  • deps 为 [] 表示只在组件挂载和卸载时执行;

✅ 二、初始化阶段:mountEffect

function mountEffect(create, deps) {
  • 作用:组件首次渲染时创建一个 useEffect 副作用;
  • 参数说明
    • create: 用户传入的副作用函数;
    • deps: 依赖项数组(可为空);
  const hook = mountWorkInProgressHook();
  • 作用:从当前 Fiber 中获取一个新的 Hook 节点;
  • 解释:React 使用链表结构保存每个组件中的所有 Hook;
  const nextDeps = deps === undefined ? null : deps;
  • 作用:处理用户未传入 deps 的情况;
  • 说明:如果用户没有写 deps,则默认为 null,表示每次都执行 effect;
  hook.memoizedState = pushEffect(HookHasEffect | HookLayout, create, undefined, nextDeps);
  • 作用:创建并挂载 Effect 对象到当前 Hook 上;
  • 参数说明
    • HookHasEffect | HookLayout: 标记该 effect 需要执行;
    • create: 用户传入的副作用函数;
    • undefined: 清理函数初始为空;
    • nextDeps: 当前依赖项数组;
  • pushEffect 返回值:新创建的 Effect 对象;

✅ 三、更新阶段:updateEffect

function updateEffect(create, deps) {
  • 作用:组件后续更新时复用已有的 Hook,并判断是否需要执行 effect;
  • 参数说明
    • create: 用户传入的副作用函数;
    • deps: 新传入的依赖项数组;
  const hook = updateWorkInProgressHook();
  • 作用:从当前 Fiber 中获取对应的 Hook 节点;
  • 注意:必须保持调用顺序一致,否则会导致状态错乱!
  const nextDeps = deps === undefined ? null : deps;
  • 作用:处理用户未传入 deps 的情况;
  • 说明:同上,用于判断依赖是否变化;
  const prevEffect = hook.memoizedState;
  • 作用:获取上一次保存的 Effect 对象;
  • 说明:包含上一次的 create 函数和 deps
  const prevDeps = prevEffect.deps;
  • 作用:获取上一次的依赖项数组;
  • 说明:用于与本次传入的 deps 比较;
  if (areHookInputsEqual(nextDeps, prevDeps)) {
  • 作用:比较依赖项是否发生变化;
  • 说明areHookInputsEqual 是浅比较(shallow equal);
  • 如果依赖项未变,则跳过执行 effect;
    hook.memoizedState = pushEffect(HookNone, create, undefined, nextDeps);
  • 作用:标记该 effect 不需要执行;
  • 参数说明
    • HookNone: 表示不需要执行 effect;
    • 其他参数不变;
  } else {
    hook.memoizedState = pushEffect(HookHasEffect | HookLayout, create, undefined, nextDeps);
  • 作用:依赖项变化,标记该 effect 需要执行;
  • 说明HookHasEffect 表示需要调度执行该 effect;
  }

✅ 四、Effect 队列结构详解

export type Effect = {
  tag: number,          // 标记 effect 是否执行或清理
  create: () => mixed,  // 用户传入的副作用函数
  destroy: (() => mixed) | null, // 清理函数
  deps: Array<mixed> | null,     // 依赖项数组
  next: Effect,         // 指向下一个 Effect,形成链表
};
字段名 含义
tag 表示 effect 的状态标志(如 HookHasEffect, HookNone
create 用户传入的副作用函数
destroy 上次执行后的清理函数(可能为 null)
deps 依赖项数组,用于判断是否重新执行 effect
next 指向下一个 Effect,构成链表结构

五、useEffect 的算法设计思想与执行步骤总结

1. 核心目标

提供一种声明式副作用管理机制,支持:

  • 组件挂载后执行副作用;
  • 依赖项变化时重新执行副作用;
  • 组件卸载前执行清理逻辑;
  • 支持异步调度(如 layout / passive effect);
  • 多个 useEffect 按顺序执行;

2. 设计思想

设计思想 实现方式
链表结构 每个 Hook 是链表节点,通过 next 指针连接
双缓冲机制 current 和 workInProgress Hook 分离
副作用队列 所有 effect 被收集到 effect list 中统一提交
依赖项比较 浅比较 deps 判断是否重新执行 effect
生命周期分离 useLayoutEffect 在 DOM 更新前执行,useEffect 在之后异步执行

3. 执行流程步骤详解

步骤 1:创建 Hook 链表节点
  • 每次调用 useEffect 都会创建一个 Hook 节点;
  • 所有 Hook 按顺序串联成链表;
  • Hook 节点中保存当前 effect 的状态、依赖等信息;
步骤 2:初始化 Effect
  • 创建 Effect 对象,设置 create 函数、deps 依赖;
  • 设置 tag 标志位为 HookHasEffect,表示需要执行;
  • 将 Effect 挂载到当前 Hook 的 memoizedState 属性中;
步骤 3:更新时比较依赖项
  • 获取上一次保存的 Effect;
  • 获取当前传入的 deps;
  • 使用 areHookInputsEqual 进行浅比较;
  • 如果未变化,标记为不执行 effect;
  • 如果变化,标记为需要执行 effect;
步骤 4:收集 effect list
  • 所有被标记为 HookHasEffect 的 effect 会被收集到 effect list
  • 在 commit 阶段统一执行这些 effect;
步骤 5:执行与清理
  • 在 commit 阶段执行 effect;
  • 如果上次存在 cleanup 函数,则先执行清理;
  • 执行新的 create 函数;
  • 保存新的 cleanup 函数供下次执行前调用;
步骤 6:组件卸载时清理所有 effect
  • 所有 effect 的 cleanup 函数都会在组件卸载时执行;
  • 确保资源释放、事件解绑等操作正确完成;

以下是 useEffect 模拟实现的完整算法步骤代码,按照列出的 6 个关键步骤 进行模块化组织,每个步骤对应一个功能模块,并附有详细注释说明其作用和逻辑。


✅ 步骤 1:创建 Hook 链表节点

// =================== Hook 结构定义 ===================
function Hook() {
  this.memoizedState = null; // 存储 Effect 对象
  this.next = null; // 下一个 Hook 节点
}

// =================== 创建/获取当前 Hook 节点(链表结构)===================
let currentlyRenderingFiber = {
  memoizedState: null, // 当前 Fiber 的 Hook 链表头节点
};

let workInProgressHook = null;

function mountWorkInProgressHook() {
  const hook = new Hook();
  if (workInProgressHook === null) {
    currentlyRenderingFiber.memoizedState = hook;
  } else {
    workInProgressHook.next = hook;
  }
  workInProgressHook = hook;
  return hook;
}

function updateWorkInProgressHook() {
  let currentHook;
  if (workInProgressHook === null) {
    currentHook = currentlyRenderingFiber.memoizedState;
    workInProgressHook = {
      memoizedState: currentHook.memoizedState,
      next: null,
    };
  } else {
    currentHook = workInProgressHook;
    workInProgressHook = workInProgressHook.next;
  }
  return currentHook;
}

说明:

  • Hook 是链表中的每一个节点;
  • currentlyRenderingFiber.memoizedState 是 Hook 链表的起始点;
  • mountWorkInProgressHook()updateWorkInProgressHook() 分别用于初始化与更新阶段获取或创建 Hook;
  • 所有 Hook 按顺序串联成链表结构,React 利用这个结构保证 Hook 调用顺序一致;

✅ 步骤 2:初始化 Effect

// =================== Effect 对象定义 ===================
function Effect(tag, create, destroy, deps) {
  this.tag = tag; // 标记是否需要执行 effect
  this.create = create; // 用户传入的副作用函数
  this.destroy = destroy; // 清理函数
  this.deps = deps; // 依赖项数组
  this.next = null; // 下一个 Effect
}

const HookHasEffect = /*                */ 0b0000000001;
const HookLayout = /* layout effect   */ 0b0000000010;

function mountEffect(create, deps) {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;

  const effect = new Effect(HookHasEffect | HookLayout, create, undefined, nextDeps);
  hook.memoizedState = effect;

  pushEffect(effect); // 收集到 effect list 中
}

说明:

  • Effect 类封装了副作用的核心信息;
  • 初始化时设置 tagHookHasEffect | HookLayout 表示该 effect 需要执行;
  • create 是用户传入的副作用函数;
  • deps 是依赖项数组;
  • pushEffect(effect) 将当前 effect 加入全局 effect list,等待后续提交;

✅ 步骤 3:更新时比较依赖项

function areHookInputsEqual(nextDeps, prevDeps) {
  if (prevDeps === null || nextDeps === null) {
    return false; // 如果是首次或无依赖项,则执行
  }

  for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
    if (Object.is(nextDeps[i], prevDeps[i])) {
      continue;
    }
    return false;
  }
  return true;
}

function updateEffect(create, deps) {
  const hook = updateWorkInProgressHook();
  const prevEffect = hook.memoizedState;
  const nextDeps = deps === undefined ? null : deps;

  if (areHookInputsEqual(nextDeps, prevEffect.deps)) {
    // 依赖未变化,跳过执行 effect
    const effect = new Effect(HookNone, create, prevEffect.destroy, nextDeps);
    hook.memoizedState = effect;
  } else {
    // 依赖变化,标记为需执行
    const effect = new Effect(HookHasEffect | HookLayout, create, prevEffect.destroy, nextDeps);
    hook.memoizedState = effect;
    pushEffect(effect);
  }
}

说明:

  • areHookInputsEqual() 实现浅比较,判断依赖项是否变化;
  • 若依赖不变,标记为不执行(HookNone);
  • 若依赖变化,标记为需要执行(HookHasEffect)并加入 effect list;
  • 更新阶段通过 hook.memoizedState 保存新的 effect;

✅ 步骤 4:收集 effect list

// =================== 模拟 effect list 提交队列 ===================
const effectList = [];

function pushEffect(effect) {
  effectList.push(effect);
}

说明:

  • effectList 是一个全局数组,用于存储所有被标记为“需要执行”的 effect;
  • 在 commit 阶段统一处理这些 effect;
  • React 中会构建一个树状 effect list 并进行深度优先遍历执行;

✅ 步骤 5:执行与清理

// =================== 模拟提交阶段 ===================
function flushPassiveEffects() {
  for (const effect of effectList) {
    if (effect.tag & HookHasEffect) {
      // 执行上一次的清理函数
      if (effect.destroy) {
        effect.destroy();
      }

      // 执行新的副作用
      effect.destroy = effect.create();

      // 更新 effect list
      effect.tag &= ~HookHasEffect;
    }
  }
}

说明:

  • flushPassiveEffects() 模拟 commit 阶段;
  • 先调用上次的 cleanup 函数(如果存在);
  • 再执行当前的 create() 副作用函数;
  • 最后更新 destroy 字段,供下一轮使用;
  • 清除 HookHasEffect 标志位,避免重复执行;

✅ 步骤 6:组件卸载时清理所有 effect

if (module.hot) { 
  module.hot.accept();   
  module.hot.dispose(() => {
    // 模拟组件卸载
    workInProgressHook = null;
    currentlyRenderingFiber.memoizedState = null;
  });
}

说明:

  • 使用 module.hot.dispose() 模拟组件卸载;
  • 组件卸载时清空 Hook 链表;
  • 实际 React 中会在组件 unmount 时依次调用所有 effect 的 destroy 方法;

✅ 示例组件 + 状态管理模拟

// =================== 示例组件 ===================
function MyComponent() {
  const [count, setCount] = myUseState(0);

  myUseEffect(() => {
    console.log('mounted or updated', count);
    return () => {
      console.log('cleanup', count);
    };
  }, [count]);

  const button = document.getElementById('btn');
  button.onclick = () => {
    setCount(count + 1);
    simulateRender(); // 触发重新渲染
  };

  console.log('Current effect list:', effectList);
}

// =================== 模拟 useState ===================
let stateIndex = 0;
const memoizedState = [];

function myUseState(initialState) {
  const currentIndex = stateIndex++;
  if (memoizedState[currentIndex] === undefined) {
    memoizedState[currentIndex] = initialState;
  }

  function setState(newState) {
    memoizedState[currentIndex] = newState;
    simulateRender();
  }

  return [memoizedState[currentIndex], setState];
}

// =================== 模拟重新渲染 ===================
function simulateRender() {
  console.log('--- Re-rendering ---');
  stateIndex = 0;
  workInProgressHook = null;
  renderWithHooks();
  flushPassiveEffects();
}

function renderWithHooks() {
  workInProgressHook = null;
  currentlyRenderingFiber.memoizedState = null;
  MyComponent(); // 执行组件函数,触发 useEffect
}

说明:

  • MyComponent() 是一个简化版函数组件;
  • 使用 myUseState 模拟状态管理;
  • myUseEffect 是对 mountEffect / updateEffect 的封装;
  • 每次点击按钮触发 setCount() 后都会调用 simulateRender() 模拟重新渲染;
  • 整个流程模拟了从组件挂载、更新、卸载的生命周期;

✅ 总结:每一步的代码结构映射关系

步骤编号 功能描述 对应代码文件位置
Step 1 创建 Hook 链表节点 Hook, mountWorkInProgressHook, updateWorkInProgressHook
Step 2 初始化 Effect Effect, mountEffect, pushEffect
Step 3 更新时比较依赖项 updateEffect, areHookInputsEqual
Step 4 收集 effect list effectList, pushEffect
Step 5 执行与清理 flushPassiveEffects
Step 6 组件卸载时清理所有 effect module.hot.dispose

六、相关函数关系图(简化)

useEffect(create, deps)
       ↓
mountEffect / updateEffect
       ↓
mountWorkInProgressHook / updateWorkInProgressHook
       ↓
创建/复用 Hook 节点
       ↓
pushEffect → 构建 Effect 对象
       ↓
比较 deps → 决定是否执行 effect
       ↓
commitRoot → 提交 effect list
       ↓
执行 create 函数 + 清理逻辑

Effect 的提交与清理时机

  • 在 commit 阶段统一执行:
    • 插入 DOM 后触发 useLayoutEffect
    • 下一帧触发 useEffect
  • 清理逻辑会在下次执行前调用(如果依赖项变化);
  • 如果组件卸载,则会立即调用所有 cleanup 函数。

六、useRef 的内部实现

6.1 使用方式

const ref = useRef(0);

6.2 内部实现

以下是对 mountRefupdateRef 函数的逐行详细注释,以及完整的代码片段。这两个函数模拟了 React 的 useRef Hook 的内部实现机制。


✅ 完整代码片段(含详细注释)

/**
 * 模拟 React 内部 mount 阶段的 useRef 实现
 * 用于组件首次渲染时创建一个 ref 对象
 */
function mountRef(initialValue) {
  // 1. 创建一个新的 Hook 节点,并添加到当前 Fiber 的 Hook 链表中
  const hook = mountWorkInProgressHook();

  // 2. 创建 ref 对象,包含一个 current 属性,初始值为 initialValue
  const ref = { current: initialValue };

  // 3. 将 ref 对象保存在 hook.memoizedState 中,供后续更新阶段使用
  hook.memoizedState = ref;

  // 4. 返回 ref 对象,供开发者使用
  return ref;
}

/**
 * 模拟 React 内部 update 阶段的 useRef 实现
 * 用于组件后续更新时获取已存在的 ref 对象
 */
function updateRef(initialValue) {
  // 1. 获取当前正在处理的 Hook 节点(复用已有 Hook)
  const hook = updateWorkInProgressHook();

  // 2. 返回该 Hook 上次保存的 ref 对象(不会重新创建)
  return hook.memoizedState;
}

辅助函数说明

为了使 mountRefupdateRef 可运行,需要配合以下辅助结构和函数:

Hook 结构定义

// Hook 类:表示一个 Hook 节点
function Hook() {
  this.memoizedState = null; // 存储 Hook 的状态(如 ref、state 等)
  this.next = null;          // 指向下一个 Hook 节点,构成链表结构
}

全局变量模拟 Fiber 架构中的上下文

// 当前 Fiber 节点(模拟)
let currentlyRenderingFiber = {
  memoizedState: null, // Hook 链表头节点
};

// 当前正在构建或更新的 Hook 节点
let workInProgressHook = null;

创建/获取当前 Hook 的函数

/**
 * 初始化阶段:创建一个新的 Hook 并加入链表
 */
function mountWorkInProgressHook() {
  const hook = new Hook();
  if (workInProgressHook === null) {
    // 如果是第一个 Hook,则作为链表头节点
    currentlyRenderingFiber.memoizedState = hook;
  } else {
    // 否则链接到上一个 Hook 的 next 属性上
    workInProgressHook.next = hook;
  }
  workInProgressHook = hook;
  return hook;
}

/**
 * 更新阶段:复用已有的 Hook
 */
function updateWorkInProgressHook() {
  let currentHook;
  if (workInProgressHook === null) {
    // 第一次调用时从 current Fiber 获取 Hook
    currentHook = currentlyRenderingFiber.memoizedState;
    workInProgressHook = {
      memoizedState: currentHook.memoizedState,
      next: null,
    };
  } else {
    // 后续 Hook 直接取当前节点并移动指针
    currentHook = workInProgressHook;
    workInProgressHook = workInProgressHook.next;
  }
  return currentHook;
}

示例组件:使用自定义 useRef

function MyComponent() {
  // 使用自定义 useRef 创建一个 ref 对象
  const inputRef = mountRef(null);

  // 模拟点击按钮聚焦输入框
  const button = document.getElementById('btn');
  button.onclick = () => {
    if (inputRef.current) {
      inputRef.current.focus();
      console.log('Input focused:', inputRef.current.value);
    }
  };

  // 渲染输入框
  const input = document.createElement('input');
  input.id = 'my-input';
  input.placeholder = '请输入内容';
  input.ref = inputRef; // 模拟 ref 绑定
  inputRef.current = input; // 手动赋值 ref.current

  document.body.appendChild(input);
}

// =================== 初始化渲染 ===================
MyComponent(); // 初始挂载组件

总结:useRef 的设计思想与作用

设计点 说明
不触发重新渲染 ref 的变化不会导致组件重新渲染
保持引用稳定性 即使组件多次渲染,返回的 ref 始终是同一个对象
跨渲染周期共享数据 可以用来保存 DOM 引用、定时器 ID、任意可变状态等
支持自定义 Hook 复用逻辑 如封装 usePrevious(value)

七、Hook 的执行顺序与依赖项比较机制

7.1 Hook 必须按顺序调用

  • React 通过链表顺序来匹配 Hook 的状态;
  • 如果在条件判断中跳过某个 Hook,会导致后续 Hook 错位,出现不可预知的状态错误;
  • 因此 React 严格禁止如下写法:
if (condition) {
  const [state, setState] = useState(0); // ❌ 不允许
}

7.2 依赖项比较(deps)

  • useEffectuseMemouseCallback 等 Hook 都支持依赖项;
  • 比较方式是浅比较(shallow equal);
  • 若依赖项未变,则跳过执行;
  • 若依赖项变化,则重新执行 Hook;
  • 示例:
useEffect(() => {
  console.log('依赖项变化了');
}, [obj]); // 浅比较 obj 是否引用改变

八、设计模式分析

React Hooks 的实现中体现了多个经典设计模式:

设计模式 应用场景
链表结构(Linked List) Hook 链表保存组件状态
双缓冲机制(Double Buffering) current 与 workInProgress Fiber 的 Hook 切换
策略模式(Strategy) 不同 Hook 类型(useState、useEffect)采用不同处理策略
观察者模式(Observer) 依赖项变化触发 Hook 重新执行
命令模式(Command) 将 effect 作为命令放入队列,延迟执行
享元模式(Flyweight) 复用已有的 Hook 节点,避免重复创建

九、总结与延伸建议

本教程深入剖析了 React Hooks 的底层实现机制,包括:

  • Hooks 的链表结构与存储方式;
  • useState 的状态管理与更新队列;
  • useEffect 的副作用调度与清理逻辑;
  • useRef 的引用保持机制;
  • Hook 的执行顺序与依赖项比较;
  • 涉及的设计模式与源码结构。

延伸阅读建议:

  1. React 官方文档 - Hooks
  2. React GitHub 源码仓库 - ReactFiberHooks.js
  3. 精读《React Hooks 原理》
  4. 书籍推荐:《React 设计模式与最佳实践》《React 源码解析系列》

你可能感兴趣的:(源码学习笔记,javascript,前端,react,源码,学习,hooks,react.js)