React Hooks底层执行逻辑详解、自定义Hooks、Fiber&Scheduler

React Hooks底层执行逻辑详解

React Hooks 在表面上看像普通的函数调用,背后却隐藏着一套复杂而高效的运行时机制 。要理解 React Hooks 的底层执行逻辑,需要从 React 如何管理组件的状态与副作用入手。


🧠 一、React 为什么引入 Hooks?

在 class 组件中,状态逻辑分散、复用困难(通过 HOC、render props 实现)。Hooks 通过"函数组件 + 闭包 + 顺序调用"机制,解决了这些问题,让函数组件拥有"类"的能力。


🔍 二、Hooks 的核心机制:调用顺序与数组栈

✅ 核心思想:

React 内部为每个组件实例维护一个 "Hook 调用栈" (本质上是一个数组),Hooks 的执行顺序严格依赖于你在函数中书写的顺序

举个例子:

js 复制代码
function MyComponent() {
  const [count, setCount] = useState(0)    // 第一个 Hook(索引 0)
  const [name, setName] = useState('')     // 第二个 Hook(索引 1)
}

React 内部逻辑可以抽象为伪代码:

js 复制代码
let hookIndex = 0
const hooks = []

function useState(initialValue) {
  const currentIndex = hookIndex

  if (isMount) {
    hooks[currentIndex] = typeof initialValue === 'function' ? initialValue() : initialValue
  }

  const setState = (newValue) => {
    hooks[currentIndex] = newValue
    triggerComponentUpdate()
  }

  hookIndex++
  return [hooks[currentIndex], setState]
}

✔️ 所以:每次重新渲染组件时,React 都会以相同顺序重新执行 useXXX(),来匹配状态数组中的值。


🔧 三、Hooks 的执行流程(React 内部机制)

🧩 每次组件渲染时:

  1. 当前组件会进入 render phase

  2. React 初始化 fiberNode.memoizedState(Hook 存储区)

  3. 每次调用一个 Hook(如 useStateuseEffect):

    • React 用当前的 hookIndex 取出对应位置的值
    • 更新完后 hookIndex++
  4. 所有 Hook 调用完毕后:

    • memoizedState 就是这个组件的 Hook 状态链
    • React 将其挂在 fiber 树上,供下一次渲染使用

⚙️ 四、不同 Hook 的底层行为

1️⃣ useState

  • 在首次渲染时保存初始值
  • 后续调用 setState 会触发组件更新,并保留新值在状态数组中

2️⃣ useEffect

  • 注册副作用及清理函数
  • 保存依赖数组,用于下一次渲染对比
  • 在 commit 阶段执行副作用

3️⃣ useRef

  • 保存一个 { current: ... } 对象
  • 是在 Hook 数组中创建的稳定引用,不会重新创建

4️⃣ useMemo / useCallback

  • 保存返回值或函数引用
  • 对比依赖数组决定是否复用旧值

📊 五、Hook 状态存储结构:Fiber Node

每个组件的所有 Hook 状态,都挂载在它自己的 Fiber Node 上的 memoizedState 字段中 ,它实际上是一个单链表结构

txt 复制代码
FunctionComponentFiberNode
└── memoizedState --> HookState(useState) 
                     └── next --> HookState(useEffect) 
                                   └── next --> ...

这个结构意味着:

  • 每个 Hook 对应链表中的一个节点
  • 遍历顺序必须与调用顺序保持一致,否则状态将错位(所以 Hooks 不能写在条件语句中)

🚨 六、为什么 Hooks 不能放在 if 语句里

因为状态顺序是通过"调用顺序 + 索引"来维护的。如果你这样写:

js 复制代码
if (someCondition) {
  useState(...) // ⚠️ 不一定会调用
}

那么下一次渲染时,Hook 的数量或顺序可能变化,导致状态错乱(取到了别的 Hook 的状态)!

React 会通过内部开发环境检查这些不规范用法。


🧬 七、Hook 触发组件更新的机制

当调用 setState(newVal)

  1. 会创建一个更新对象,加入到组件的更新队列
  2. 标记当前 Fiber 为"需要更新"
  3. 触发 React Scheduler 安排任务(基于优先级)
  4. 进入 render phase,重新执行组件函数(重新执行所有 Hooks)
  5. 比较 Fiber 树 → 更新 DOM

🧪 八、一个模拟实现:useState

js 复制代码
let hookStates = []
let hookIndex = 0

function useState(initialValue) {
  const currentIndex = hookIndex
  hookStates[currentIndex] = hookStates[currentIndex] || initialValue

  function setState(newVal) {
    hookStates[currentIndex] = newVal
    render()
  }

  hookIndex++
  return [hookStates[currentIndex], setState]
}

function render() {
  hookIndex = 0
  ReactDOM.render(<App />, document.getElementById('root'))
}

✅ 九、总结:Hooks 底层关键点

机制 说明
顺序执行原则 Hooks 必须按照一致顺序调用
状态数组/链表结构 每个 Hook 都在 Fiber 节点的状态链表中占一项
更新触发原理 setState 会触发组件的调度和重新渲染
Hooks 本质 React 自定义的一套状态和副作用管理系统,依赖"闭包 + 引用 + 顺序"维护状态

自定义Hook

自定义 Hooks 是现代 React 中最重要的模式之一,用于在函数组件之间复用逻辑。它是一种在组件外提取公共逻辑的方式,优雅地替代了以前 class 组件中用 HOC 或 render props 的做法。


🧠 一、自定义 Hook 是什么?

自定义 Hook 就是一个以 use 开头的 JavaScript 函数,内部可以调用其他 Hooks(如 useState、useEffect、useContext 等)

它并不需要拥有特殊的语法,而是遵守命名规范(以 use 开头)和 Hook 规则(只能在顶层和 React 函数组件中调用)


🔧 二、基础语法结构

jsx 复制代码
function useMyHook() {
  const [state, setState] = useState(initialValue)

  // 可以包含副作用
  useEffect(() => {
    // 例如订阅数据
    return () => {
      // 清理操作
    }
  }, [])

  return { state, setState }
}

使用方法:

jsx 复制代码
function MyComponent() {
  const { state, setState } = useMyHook()

  return <div>{state}</div>
}

🛠️ 三、常见自定义 Hook 示例

1️⃣ useWindowSize:监听窗口大小

js 复制代码
import { useState, useEffect } from 'react'

function useWindowSize() {
  const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight })

  useEffect(() => {
    const handleResize = () => {
      setSize({ width: window.innerWidth, height: window.innerHeight })
    }

    window.addEventListener('resize', handleResize)
    return () => window.removeEventListener('resize', handleResize)
  }, [])

  return size
}

2️⃣ useFetch:封装数据请求逻辑

js 复制代码
import { useState, useEffect } from 'react'

function useFetch(url) {
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    setLoading(true)
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data)
        setLoading(false)
      })
  }, [url])

  return { data, loading }
}

使用:

jsx 复制代码
const { data, loading } = useFetch('/api/user')

🧩 四、自定义 Hook 的使用场景

场景 Hook 示例
状态共享 useForm, useTheme, useAuth
业务逻辑抽象 usePagination, useLogin
操作 DOM useScroll, useFocus
事件/副作用封装 useOnlineStatus, useDebounce
状态机器或流程控制 useWizard, useStepper

🚨 五、自定义 Hook 的注意事项

1. 必须以 use 开头

否则 React 无法检测是否遵守 Hook 的规则。

2. 遵循 Hook 规则(顶层调用)

  • 不要在 if、for、事件中调用 Hook
  • Hook 必须在函数组件或另一个 Hook 中使用


Hooks 是按调用顺序管理状态的,调用顺序必须稳定执行上下文必须是"可追踪"的(函数组件或 Hook),否则 React 无法正确管理它们。

3. 避免副作用重复(加依赖)

js 复制代码
useEffect(() => {
  // fetch or something
}, [url]) // 依赖项必须写全

✅ 六、自定义 Hook vs HOC/Render Props

特性 自定义 Hook HOC Render Props
写法简洁 ✅ 非常简洁 ❌ 会嵌套 ❌ 需要 props 回调
类型推导好 ✅ 原生支持 ❌ props 类型麻烦
可组合性 ✅ 任意组合
推荐程度 ✅ 推荐使用 ⛔ 旧方式(不推荐) ⛔(不推荐)

📦 七、如何组织自定义 Hook?

建议创建一个 hooks/ 文件夹,每个 Hook 一个文件。例如:

复制代码
src/
├── hooks/
│   ├── useAuth.js
│   ├── useFetch.js
│   └── useWindowSize.js

✨ 总结

你应该使用自定义 Hook 的时候:
有重复逻辑(如监听、请求、共享状态等)
逻辑独立但不涉及 UI
想增强可维护性和可测试性

如果你告诉我你项目中具体遇到的重复逻辑或问题,我可以帮你定制一个适合你项目的自定义 Hook 模板。是否要尝试?


Fiber&Scheduler

Fiber 架构 + Scheduler 是 React 实现 并发渲染 的核心。它们分别负责「任务的组织与执行逻辑」和「任务的调度与执行时机」。


🧠 一、React Fiber 架构详解

Fiber 是 React 16 引入的全新架构,用来替代旧的 Stack Reconciler。它的设计目标是:

  • 任务可被拆分(增量渲染)
  • 渲染可中断、恢复、重用
  • 支持并发调度

📦 1. Fiber 是什么?

一个 Fiber 就是一个组件的工作单元(Work Unit),它是一个 JavaScript 对象,描述了:

js 复制代码
type FiberNode = {
  type: Function | string;
  key: string | null;
  child: FiberNode | null;
  sibling: FiberNode | null;
  return: FiberNode | null; // 父节点
  stateNode: any; // 组件实例
  memoizedProps: any;
  memoizedState: any;
  alternate: FiberNode | null; // 双缓冲
  flags: number; // 副作用标记
  ...
}

🧭 2. Fiber Tree 构建过程

每次更新时,React 会创建一棵新的 Fiber 树(work-in-progress tree),由当前树(current tree)复制并修改。

  • 双缓冲机制:current & workInProgress 交替使用
  • 每个更新任务递归生成 Fiber 节点,构成完整 Fiber 树

🔁 3. Fiber 的工作循环(核心阶段)

🔨 Reconciliation(协调阶段)
  • 调用组件函数(或类的 render)生成新的虚拟 DOM
  • 比较新旧 Fiber 树,标记哪些节点需要变更
  • 构建 "Effect List"(副作用链表)
🚀 Commit(提交阶段)
  • 根据 Effect List 执行真实的 DOM 操作(插入/删除/更新)
  • 不可被打断,必须同步完成

🕹️ 二、Scheduler 调度器详解

Scheduler 是 React 的调度核心,负责 管理 Fiber 的执行时机与优先级,使 React 拥有「可中断」和「可恢复」的能力。


📋 1. 核心能力

  • 管理任务队列
  • 计算任务优先级(Lanes)
  • 根据浏览器空闲时间切片执行
  • 决定是否让出主线程(shouldYield

⏳ 2. 时间切片(Time Slicing)

ts 复制代码
while (work && !shouldYield()) {
  work = performUnitOfWork(work);
}
  • 每次只做一小部分工作(一个 Fiber 节点)
  • 超出时间阈值就让出执行权,避免卡住主线程

📊 3. 优先级系统(Lanes)

React 引入「Lanes(车道)」作为多优先级调度方案:

ts 复制代码
const NoLane = 0b00000
const SyncLane = 0b00001       // 同步优先级
const InputContinuousLane = 0b00010 // 用户输入优先
const DefaultLane = 0b00100
const IdleLane = 0b10000       // 最低优先

React 会根据任务类型分配 Lane,调度器根据当前空闲情况,调度最高优先的任务先执行。


⚙️ 三、Fiber + Scheduler 协同流程图

复制代码
用户触发更新(如点击按钮)
      ↓
Scheduler 收到任务,放入任务队列
      ↓
根据 Lane 决定优先级 & 是否立即执行
      ↓
Fiber Tree 被构建(协调阶段)
      ↓
每个 Fiber 任务以时间切片形式执行
      ↓
中途检查 shouldYield(),必要时中断
      ↓
所有 Fiber 构建完成,进入 commit 阶段
      ↓
一次性提交 DOM 修改(同步执行)

🧩 总结:Fiber & Scheduler 分工

功能 Fiber Scheduler
结构 描述每个组件的渲染任务 管理任务执行的优先级和时机
拆分任务 将渲染工作拆成一个个 Fiber 节点 将任务切片处理,防止阻塞主线程
可中断机制 通过时间切片,暂停和恢复渲染 决定何时中断、恢复和重新调度任务
优先级处理 每个 Fiber 带有优先级(lane) 任务队列按优先级排序

如果你需要我通过可视化图解、源码级解析(如 Scheduler 源码调度流程),我也可以帮你补充。是否继续?

相关推荐
风无雨4 分钟前
GO启动一个视频下载接口 前端可以边下边放
前端·golang·音视频
Rainbow_Pearl9 分钟前
Vue2_element 表头查询功能
javascript·vue.js·elementui
aha-凯心1 小时前
前端学习 vben 之 axios interceptors
前端·学习
熊出没1 小时前
Vue前端导出页面为PDF文件
前端·vue.js·pdf
VOLUN1 小时前
Vue3项目中优雅封装API基础接口:getBaseApi设计解析
前端·vue.js·api
此乃大忽悠1 小时前
XSS(ctfshow)
javascript·web安全·xss·ctfshow
用户99045017780091 小时前
告别广告干扰,体验极简 JSON 格式化——这款工具让你专注代码本身
前端
前端极客探险家1 小时前
告别卡顿与慢响应!现代 Web 应用性能优化:从前端渲染到后端算法的全面提速指南
前端·算法·性能优化
袁煦丞2 小时前
【局域网秒传神器】LocalSend:cpolar内网穿透实验室第418个成功挑战
前端·程序员·远程工作
江城开朗的豌豆2 小时前
Vuex数据突然消失?六招教你轻松找回来!
前端·javascript·vue.js