当前位置: 首页 > news >正文

react hook 原理

前言:

本篇文章为作者拜读外星人大佬文章所感受,旨在让更多的小白也能看懂理解,特来写文章分享感受,如有不对请多多指正(原文链接「react进阶」一文吃透react-hooks原理之前的两篇文章,分别介绍了react-hooks如何使用,以及自定义 - 掘金)

一、react fiber渲染机制

在了解reacthook之前,我们要先了解一下react fiber更新机制:

React fiber是react 16的渲染机制,他将react的组件树采用链表的方式进行切割,变为更细的fiber树,各个fiber之间支持任务的中断、恢复和优先级调度,防止浏览器渲染的时候阻塞一些更高优先级的点击输入等事件。

React fiber分为render和commit两个状态,rander状态用来计算需要更新的fiber树,计算diff差异,可以被中断,commit提交时来操作实际dom,不可被中断。

二、普通函数和组件函数

React 中的函数大致可以分为普通函数和组件函数。
普通函数就是普通的 JavaScript 函数,一般用于封装计算逻辑或工具方法,调用方式和 JS 函数一样,例如 sum(a, b)。
组件函数本质上也是 JavaScript 函数,但它会被 React 当成组件来渲染。函数组件通常首字母大写,返回 JSX 结构,使用时通过 这种标签形式。React 在第一次渲染函数组件时,会通过 renderWithHooks 执行组件函数,因此函数组件内部可以在顶层调用 useState、useEffect 等 Hook。

三、react Hook初始化流程

(1)renderWithHooks初始化环境

renderWithHooks会在组件函数被渲染的时候启用,它会初始化这个组件函数的如下参数:

1.current Fiber:当前组件内容树,在提交阶段会转化真正的dom树。初始为空

  1. workInProgress Fiber:本次要渲染的组件内容树。初始为当前组件初始组件内容树

  2. Component:组件本身

4.props:传进来的参数值

  1. context/secondArg: 传给组件的第二个参数,普通组件基本不用管

  2. renderLanes:组件组件渲染优先级

然后根据这个组件函数是否是第一次渲染,赋予ReactCurrentDispatcher.current不同的hooks。ReactCurrentDispatcher.current决定了hook的执行规则(也就是 useState、useEffect 等 Hook 最终会执行哪一套具体实现。)

对于第一次渲染组件,那么用的是HooksDispatcherOnMount hooks对象。 对于渲染后,需要更新的函数组件,则是HooksDispatcherOnUpdate对象,那么两个不同就是通过current树上是否memoizedState(hook信息)来判断的。如果current不存在,证明是第一次渲染函数组件。

(2)组件函数执行

调用Component(props, secondArg);执行我们的函数组件,我们的函数组件在这里真正的被执行了,此时,我们写的hooks也被依次执行。执行之后Components把hooks信息依次保存到workInProgress树上。最终函数返回jsx内容

(3)renderWithHooks清扫环境

ReactCurrentDispatcher.current置为ContextOnlyDispatcher ,此时渲染阶段结束,大部分hook函数再执行就会报错,防止回调函数等。最后再置空一些变量比如currentHook等等。

四、mountWorkInProgressHook挂载阶段创建工作树

初次渲染函数组件时启用,每次执行hook函数的时候都会创建一个hook对象,hook对象之间用链表串连在一起。最终mountWIPHook将链表返回,并将其挂载到WIP的memoizedState上。

Hook内的信息如下:

1.memoizedState:当前最新状态
2.baseState:当前优先级重播的起始位置,二者的值都由newState赋予
3.baseQueue:上一次被跳过的更新队列
4.queue:本次的更新队列
5.next:下一个hook地址

这么说大家也可能有点乱,我特意给大家梳理了一张图来便于理解

接下来我们来看看不同的hook函数在挂载阶段都有什么表现吧

(1)useState 状态如何变化

const [num,setNum] = useState(0)

useState在初始化的时候为mountState。首先mountState将初始化的state传给,经过mountWIPSHook初始化的hook里面的memoizedState和baseState,然后创建queue队列取保存更新信息。

useState的更新方法为dispatchAction,也就是我们的setNumber。dispatchAction的第一个和第二个参数已经被bind绑定为currentlyRenderingFiber和 queue。当调用setState的时候,dispatchAction会产生一个update对象来记录本次的修改信息,并把他放到hook的queue中。接下来判断react是否在渲染中,若在渲染中则标记,当前渲染结束后react会重新计算;不在渲染中则提前算出值并进行浅比较,结果相同则不更新,不同则进行更新。注意render是react来操作的,dispatchAction只是检测当前状态和通知react更新渲染。

(2)useEffect 变化之后,我们能做什么

useEffect(()=>{},[])

useEffect在初始化的时候也是mountEffect,接收create函数和deps依赖。mountEffect先用mountWIPHooks()的dispatch创建effect自己的hook对象,将create函数放到hook的memoizedState里面,并将该hook对象挂载到fiber树的memoizedState链表里面。接下来,mountEffect调用pushEffect方法,将useEffect创建一个effect对象,将其挂载到fiber树的updateQueue中。Effect对象中包含
1.tag 是否要重新执行create函数
2.create生成函数,是useEffect的第一个参数--回调函数
3.destroy销毁函数,也就是return里面的
4.deps依赖
5.next:下一个effect对象的位置

所以总体流程如下:

Mount阶段:函数组件先按照组件顺序初始化hook函数。 当外部触发更新事件的时候,react将useState等的hook变化存到queue里面

Render阶段:函数组件先按照顺序运行hook函数,若queue里面有更新变化则更新useState的值。当useState等hook函数执行完成,进入到更新effect阶段(因为effect的依赖项要在effect之前已经声明好)effect根据传入的deps和原来的deps进行浅比较,若变化则打上tag变化。

Commit 阶段:根据WIP fiber和current fiber进行对比,进行dom更新,更新结束后,将WIP fiber赋值给 current fiber

passive effect阶段:react扫描fiber里的updateQueue链表,对里面的tag进行判断进行重新执行

(3)useMemo 缓存计算值

useMemo在初始化采用mountMemo,mountMemo先初始化hooks,然后将函数计算的结果和依赖项以数组的形式存在hook的memoizedState中

(4)useRef 保存变量

useRef 在初始化执行mountRef,mountRef先初始化hook,然后将初始化的值存成一个对象{current:initalValue}(这样才能保证每次渲染拿到的值都有稳定的地址)。最后将这个对象存到hook的memoizedState中。

先写到这里(真的是太难啦,写作不易,多多指正)

http://www.jsqmd.com/news/1112745/

相关文章:

  • 2026年健康早餐新选择:揭秘最受欢迎的苦荞片品牌
  • AI Agent赋能外贸客户开发:从电梯行业实战看自动化精准获客
  • IDA Pro Linux二进制逆向分析:从静态分析到动态调试实战指南
  • Z-Blog vs WordPress 多语言方案深度对比:中小站出海到底该选谁?
  • 2026最新8款vibe coding工具平替深度实测合集
  • 避开Claude Code七大深坑,AI编程代理效率提升50%
  • SpringBoot整合Redis实战:从配置到分布式锁
  • AI Agent落地难的真相:业务耦合与效果归因实战指南
  • FPGA与STM32的SPI通信 - FPGA主 STM32从
  • 如何3步搞定FOFA资产搜索?网络安全新手快速上手指南
  • 筑牢数字经济的“能源底座”——数据中心综合能效管理方案全解析
  • MCP与Spring AI整合实战:云原生与AI技术融合指南
  • AI辅助项目开发:从技术选型到代码优化的实战指南
  • 大模型微调实战:从LoRA到LLaMA-Factory的完整指南
  • 分享2篇最新Skill+Harness技术,组合无敌
  • 【计算机Java毕业设计案例】基于 SpringBoot 的线上教学资源整合推送系统的设计与实现 基于 SpringBoot 的成人远程继续教育管理平台(程序+文档+讲解+定制)
  • 免费开源项目文档:基于MATLAB图像处理的人脸识别签到系统设计与实现
  • CPT外汇:用视角方式看外汇行业合规表达,更容易形成稳定判断
  • Makefile基础使用
  • TDC7201与TDC7200芯片寄存器功能概述及main.c代码
  • 服务器内存与CPU协同工作知识测试题
  • 阿里terway源码分析
  • likeadmin-api 怎么做计费?从余额查询到点数消耗的接口设计
  • 2026年优选指南:探寻最佳服务的苦荞全麦片品牌
  • HAL库代码基础介绍
  • 每日技术推荐(全栈/游戏/应用开发)
  • 从 has.showToast 看 ASCF 的 API 调用链路
  • 一些碎碎念qjl--6
  • 手写 MCP Server 连数据库:50 行代码让 AI 学会查 SQL
  • 企业AI转型困境与能力建设实战指南