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

发布于:2025-05-24 ⋅ 阅读:(17) ⋅ 点赞:(0)

React Hooks底层执行逻辑详解

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


🧠 一、React 为什么引入 Hooks?

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


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

✅ 核心思想:

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

举个例子:

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

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

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 字段中,它实际上是一个单链表结构

FunctionComponentFiberNode
└── memoizedState --> HookState(useState) 
                     └── next --> HookState(useEffect) 
                                   └── next --> ...

这个结构意味着:

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

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

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

if (someCondition) {
  useState(...) // ⚠️ 不一定会调用
}

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

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


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

当调用 setState(newVal)

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

🧪 八、一个模拟实现:useState

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 函数组件中调用)


🔧 二、基础语法结构

function useMyHook() {
  const [state, setState] = useState(initialValue)

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

  return { state, setState }
}

使用方法:

function MyComponent() {
  const { state, setState } = useMyHook()

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

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

1️⃣ useWindowSize:监听窗口大小

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:封装数据请求逻辑

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

使用:

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. 避免副作用重复(加依赖)

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 对象,描述了:

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)

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

📊 3. 优先级系统(Lanes)

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

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 源码调度流程),我也可以帮你补充。是否继续?


网站公告

今日签到

点亮在社区的每一天
去签到