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

React16源码: React中处理ref的核心流程源码实现

ref的实现过程


1 )概述

  • 在更新流程当中如何去设置ref上面的对象的过程
  • 在我们创建fiber的时候去处理ref这个属性
  • 那我们什么时候创建fiber对象?
    • 就是我们去更新某一个节点,然后要去调和它的子节点的时候
    • 这个时候我们会对每一个子节点去创建这个fiber对象
  • 创建这个fiber对象的过程,我们就会去处理这个ref
  • commit开始之前先detach

2 )源码

定位到 packages/react-reconciler/src/ReactChildFiber.js#L1108

function reconcileSingleElement(returnFiber: Fiber,currentFirstChild: Fiber | null,element: ReactElement,expirationTime: ExpirationTime,
): Fiber {// ... 跳过很多代码while (child !== null) {// TODO: If key === null and child.key === null, then this only applies to// the first item in the list.if (child.key === key) {if (child.tag === Fragment? element.type === REACT_FRAGMENT_TYPE: child.elementType === element.type) {// ... 跳过很多代码// 注意这里existing.ref = coerceRef(returnFiber, child, element);// ... 跳过很多代码} else {deleteRemainingChildren(returnFiber, child);break;}} else {deleteChild(returnFiber, child);}child = child.sibling;}if (element.type === REACT_FRAGMENT_TYPE) {// ... 跳过很多代码return created;} else {// ... 跳过很多代码// 注意这里created.ref = coerceRef(returnFiber, currentFirstChild, element);created.return = returnFiber;return created;}
}
  • 进入 coerceRef
    function coerceRef(returnFiber: Fiber,current: Fiber | null,element: ReactElement,
    ) {let mixedRef = element.ref; // 拿到 ref// function ref 和 object ref 是不需要经过特殊处理的// 自己处理节点对象挂载到 class component 的 this 上面这一个过程// 对于 string ref,它只是一个string,它没有任何功能,它的挂载是要react这边来帮着去做的// 所以这边主要去 去处理一下 string ref 的一个实现过程if (mixedRef !== null &&typeof mixedRef !== 'function' &&typeof mixedRef !== 'object') {if (__DEV__) {// 跳过}// 在 ReactElement.js 中可看到  _owner 是 ReactCurrentOwner.current// 在更新 class component 的时候,调用 finishClassComponent 就会设置// ReactCurrentOwner.current = workInProgress// 在后面调用 instance.render() 去重新渲染子节点的过程中// 就会调用 React.createElement, 因为 ref 只有// 因为ref它肯定是在 class component,它这个过程当中才会被创建的// 因为只有 class component 有 this 去挂载 ref 的那个对象// 所以我们在调用 instance.render() 的时候,那么在 react element 里面拿到的这个 ReactCurrentOwner.current// 就是我们那个 class component,它对应的fiber对象if (element._owner) {const owner: ?Fiber = (element._owner: any);let inst;// 有了这个 fiber 对象之后,那么我们可以拿到它的 _owner,拿到它的 _owner 之后// 如果 owner 的存在,ownerFiber 就等于 ownerif (owner) {const ownerFiber = ((owner: any): Fiber);invariant(ownerFiber.tag === ClassComponent,'Function components cannot have refs.',);// 然后它的 inst 就是 ownerFiber.stateNode// 也就是我们 class component,那个 instance 也就是 thisinst = ownerFiber.stateNode;}invariant(inst,'Missing owner for string ref %s. This error is likely caused by a ' +'bug in React. Please file an issue.',mixedRef,);const stringRef = '' + mixedRef;// Check if previous string ref matches new string ref// 有了 inst 之后,可以为它去构建一个方法// 这边是一个对比,就是说我们每次设置完这个 ref 之后,都会给它设置一个属性 _stringRef// 用来在我们更新这个组件的过程当中,去判断一下它这个 _stringRef 对应的那个值是否有变化if (current !== null &&current.ref !== null &&typeof current.ref === 'function' &&current.ref._stringRef === stringRef) {// 如果没有变化,我们不需要去为它重新生成一个方法了,我们只需要 return 就可以了return current.ref;}// 对于新的情况,我们就需要去生成一个方法// 这个 value 就是后期dom节点或者是class instance它被挂载的时候// 它会调用这个 ref 这个方法,然后传入它自己的那个实例// 也就是给一个 dom 节点设置了 ref 之后,在后期就是 commitRoot 的过程当中,这个节点最终被挂载到 dom 上面了// 这个时候会把这个 dom 节点去调用 ref 这个方法,然后作为参数传入进来// 这个时候去设置到就是当前去创建这个 ref 的时候,这个 class component的对象上面的 ref 这个属性上面// 也就达到了 ref 可以设置在 this.refs 上面这个功能const ref = function(value) {let refs = inst.refs;if (refs === emptyRefsObject) {// This is a lazy pooled frozen object, so we need to initialize.refs = inst.refs = {};}if (value === null) {delete refs[stringRef];} else {refs[stringRef] = value;}};ref._stringRef = stringRef;return ref;} else {invariant(typeof mixedRef === 'string','Expected ref to be a function, a string, an object returned by React.createRef(), or null.',);invariant(element._owner,'Element ref was specified as a string (%s) but no owner was set. This could happen for one of' +' the following reasons:\n' +'1. You may be adding a ref to a function component\n' +"2. You may be adding a ref to a component that was not created inside a component's render method\n" +'3. You have multiple copies of React loaded\n' +'See https://fb.me/react-refs-must-have-owner for more information.',mixedRef,);}}return mixedRef;
    }
    
    • 这就是在调和子节点的过程当中,要处理 ref 的一个内容
    • 因为 stringRef 它是一个特殊的存在,它没有什么功能性
    • 而对于 function ref 传进来的就是一个方法,可以直接调用它
    • 而对于 object ref,只需要设置它的 .current 就可以了
    • 这也是为什么以后 string ref 要被移除的一个原因
    • 因为它比较麻烦,需要我们自己去处理
    • 这是我们去处理 ref 这个属性的过程

关于 commit开始之前先detach
定位到 packages/react-reconciler/src/ReactFiberScheduler.js#L392

查看 commitAllHostEffects

function commitAllHostEffects() {while (nextEffect !== null) {if (__DEV__) {ReactCurrentFiber.setCurrentFiber(nextEffect);}recordEffect();const effectTag = nextEffect.effectTag;if (effectTag & ContentReset) {commitResetTextContent(nextEffect);}// 对于有 ref 这个 SideEffect 的节点// 如果current不等于null,要先调用 commitDetachRef// 先把这个 ref 从之前挂载的地方去给它卸载下来,看下这个  commitDetachRefif (effectTag & Ref) {const current = nextEffect.alternate;if (current !== null) {commitDetachRef(current);}}// The following switch statement is only concerned about placement,// updates, and deletions. To avoid needing to add a case for every// possible bitmap value, we remove the secondary effects from the// effect tag and switch on that value.let primaryEffectTag = effectTag & (Placement | Update | Deletion);switch (primaryEffectTag) {case Placement: {commitPlacement(nextEffect);// Clear the "placement" from effect tag so that we know that this is inserted, before// any life-cycles like componentDidMount gets called.// TODO: findDOMNode doesn't rely on this any more but isMounted// does and isMounted is deprecated anyway so we should be able// to kill this.nextEffect.effectTag &= ~Placement;break;}case PlacementAndUpdate: {// PlacementcommitPlacement(nextEffect);// Clear the "placement" from effect tag so that we know that this is inserted, before// any life-cycles like componentDidMount gets called.nextEffect.effectTag &= ~Placement;// Updateconst current = nextEffect.alternate;commitWork(current, nextEffect);break;}case Update: {const current = nextEffect.alternate;commitWork(current, nextEffect);break;}case Deletion: {commitDeletion(nextEffect);break;}}nextEffect = nextEffect.nextEffect;}if (__DEV__) {ReactCurrentFiber.resetCurrentFiber();}
}

定位到 packages/react-reconciler/src/ReactFiberCommitWork.js#L623

查看 commitDetachRef

function commitDetachRef(current: Fiber) {const currentRef = current.ref;if (currentRef !== null) {if (typeof currentRef === 'function') {currentRef(null);} else {currentRef.current = null;}}
}

commitAllLifeCycles

function commitAllLifeCycles(finishedRoot: FiberRoot,committedExpirationTime: ExpirationTime,
) {// ... 跳过很多代码while (nextEffect !== null) {// ... 跳过很多代码// 注意这里if (effectTag & Ref) {recordEffect();commitAttachRef(nextEffect);}// ... 跳过很多代码}
}
  • 再次调用 commitAttachRef 把真正的更新过后的节点给它挂载上去
    function commitAttachRef(finishedWork: Fiber) {const ref = finishedWork.ref;if (ref !== null) {const instance = finishedWork.stateNode;let instanceToUse;switch (finishedWork.tag) {case HostComponent:instanceToUse = getPublicInstance(instance); // 获取到了 dom节点对应到的实例break;default:instanceToUse = instance;}// function 的处理if (typeof ref === 'function') {ref(instanceToUse);} else {// 跳过if (__DEV__) {if (!ref.hasOwnProperty('current')) {warningWithoutStack(false,'Unexpected ref object provided for %s. ' +'Use either a ref-setter function or React.createRef().%s',getComponentName(finishedWork.type),getStackByFiberInDevAndProd(finishedWork),);}}// 其他情况,直接设置ref.current = instanceToUse;}}
    }
    
    • 如果是 HostComponent 执行 getPublicInstance
      export function getPublicInstance(instance: Instance): * {return instance;
      }
      
  • 这个时候就完成了对于我们的 class component 上面的this,去挂载ref它的一个过程
  • 这边最主要的是去注意对于 stringRef 在调和子节点的过程当中
  • 会对它进行一个预先的处理,把它转化成一个方法
  • 以上就是ref在整个react应用更新的过程当中,如何被实现的原理

相关文章:

React16源码: React中处理ref的核心流程源码实现

ref的实现过程 1 )概述 在更新流程当中如何去设置ref上面的对象的过程在我们创建fiber的时候去处理ref这个属性那我们什么时候创建fiber对象? 就是我们去更新某一个节点,然后要去调和它的子节点的时候这个时候我们会对每一个子节点去创建这个fiber对象…...

ref和reactive

看尤雨溪说:为什么Vue3 中应该使用 Ref 而不是 Reactive?...

掌握数据预测的艺术:线性回归模型详解

线性回归是统计学中用于建模两个或多个变量之间线性关系的一种方法,广泛应用于数据分析、机器学习等领域。从数学建模的角度出发,线性回归旨在找到一个线性方程,最好地描述自变量(或称为解释变量、特征变量)和因变量(或称为目标变量)之间的关系。本文将通过Python代码示…...

STM32F407移植OpenHarmony笔记8

继上一篇笔记,成功开启了littlefs文件系统,能读写FLASH上的文件了。 今天继续研究网络功能,让控制台的ping命令能工作。 轻量级系统使用的是liteos_m内核lwip协议栈实现网络功能,需要进行配置开启lwip支持。 lwip的移植分为两部分…...

C++:输入流/输出流

C流类库简介 C为了克服C语言中的scanf和printf存在的缺点。,使用cin/cout控制输入/输出。 cin:表示标准输入的istream类对象,cin从终端读入数据。cout:表示标准输出的ostream类对象,cout向终端写数据。cerr&#xff…...

十、Qt三维图表

一、Data Visualization模块概述 Data Visualization的三维显示功能主要有三种三维图形来实现,三各类的父类都是QAbstract3DGraph,从QWindow继承而来。这三类分别是:三维柱状图Q3DBar三维空间散点Q3DScatter三维曲面Q3DSurface 1、相关类的…...

CMake官方教程中文翻译 Step 6: Adding Support for a Testing Dashboard

鉴于自己破烂的英语,所以把cmake的官方文档用 谷歌翻译 翻译下来方便查看。 英语好的同学建议直接去看cmake官方文档(英文)学习:地址 点这里 或复制:https://cmake.org/cmake/help/latest/guide/tutorial/index.html …...

【leetcode】完全背包总结

本文内容参考了代码随想录&#xff0c;并进行了自己的总结。 完全背包 关键点 ● 每件物品有若干种状态&#xff1a;不选、选 1 件、选 2 件、…、选 n 件 代码 在代码上&#xff0c;只有重量的遍历方向和 01 背包不一样&#xff1a; for(int i 0; i < nums.length; i…...

【Linux】理解系统中一个被打开的文件

文件系统 前言一、C语言文件接口二、系统文件接口三、文件描述符四、struct file 对象五、stdin、stdout、stderr六、文件描述符的分配规则七、重定向1. 重定向的原理2. dup23. 重谈 stderr 八、缓冲区1. 缓冲区基础2. 深入理解缓冲区3. 用户缓冲区和内核缓冲区4. FILE 前言 首…...

k8s kubeadm部署安装详解

目录 kubeadm部署流程简述 环境准备 步骤简述 关闭 防火墙规则、selinux、swap交换 修改主机名 配置节点之间的主机名解析 调整内核参数 所有节点安装docker 安装依赖组件 配置Docker 所有节点安装kubeadm&#xff0c;kubelet和kubectl 定义kubernetes源并指定版本…...

RT-DETR算法优化改进: 下采样系列 | 一种新颖的基于 Haar 小波的下采样HWD,有效涨点系列

💡💡💡本文独家改进:HWD的核心思想是应用Haar小波变换来降低特征图的空间分辨率,同时保留尽可能多的信息,与传统的下采样方法相比,有效降低信息不确定性。 💡💡💡使用方法:代替原始网络的conv,下采样过程中尽可能包括更多信息,从而提升检测精度。 RT-DET…...

CocosCreator3.8源码分析

Cocos Creator架构 Cocos Creator 拥有两套引擎内核&#xff0c;C 内核 和 TypeScript 内核。C 内核用于原生平台&#xff0c;TypeScript 内核用于 Web 和小游戏平台。 在引擎内核之上&#xff0c;是用 TypeScript 编写的引擎框架层&#xff0c;用以统一两套内核的差异&#xf…...

(已解决)spingboot 后端发送QQ邮箱验证码

打开QQ邮箱pop3请求服务&#xff1a;&#xff08;按照QQ邮箱引导操作&#xff09; 导入依赖&#xff08;不是maven项目就自己添加jar包&#xff09;&#xff1a; <!-- 邮件发送--><dependency><groupId>org.springframework.boot</groupId><…...

【蓝桥杯冲冲冲】[NOIP2001 普及组] 装箱问题

蓝桥杯备赛 | 洛谷做题打卡day26 文章目录 蓝桥杯备赛 | 洛谷做题打卡day26题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示思路 题解代码我的一些话 [NOIP2001 普及组] 装箱问题 题目描述 有一个箱子容量为 V V V&#xff0c;同时有 n n n 个物品&#xff0c;每…...

2024牛客寒假算法基础集训营1

文章目录 A DFS搜索M牛客老粉才知道的秘密G why外卖E 本题又主要考察了贪心B 关鸡C 按闹分配 今天的牛客&#xff0c;说是都是基础题&#xff0c;头昏昏的&#xff0c;感觉真不会写&#xff0c;只能赛后补题了 A DFS搜索 写的时候刚开始以为还是比较难的&#xff0c;和dfs有关…...

元素的显示与隐藏,精灵图,字体图标,CSSC三角

元素的显示与隐藏 类似网站广告&#xff0c;当我们点击关闭就不见了&#xff0c;但是我们重新刷新页面&#xff0c;会重新出现 本质&#xff1a;让元素在页面中隐藏或者显示出来。 1.display显示隐藏 2.visibility显示隐藏 3.overflow溢出显示隐藏 1.display属性&#xff08;…...

最新!2024顶级SCI优化!TTAO-CNN-BiGRU-MSA三角拓扑聚合优化、双向GRU融合注意力的多变量回归预测程序!

适用平台&#xff1a;Matlab 2023版及以上 TTOA三角聚合优化算法&#xff0c;将在2024年3月正式发表在中科院1区顶级SCI期刊《Expert Systems with Applications》上。 该算法提出时间极短&#xff0c;目前以及近期内不会有套用这个算法的文献。新年伊始&#xff0c;尽快拿下…...

Flink SQL Client 安装各类 Connector、组件的方法汇总(持续更新中....)

一般来说&#xff0c;在 Flink SQL Client 中使用各种 Connector 只需要该 Connector 及其依赖 Jar 包部署到 ${FLINK_HOME}/lib 下即可。但是对于某些特定的平台&#xff0c;如果 AWS EMR、Cloudera CDP 等产品会有所不同&#xff0c;主要是它们中的某些 Jar 包可能被改写过&a…...

React18-模拟列表数据实现基础表格功能

文章目录 分页功能分页组件有两种接口参数分页类型用户列表参数类型 模拟列表数据分页触发方式实现目录 分页功能 分页组件有两种 table组件自带分页 <TableborderedrowKey"userId"rowSelection{{ type: checkbox }}pagination{{position: [bottomRight],pageSi…...

MySQL查询数据(十)

MySQL查询数据&#xff08;十&#xff09; 一、SELECT基本查询 1.1 SELECT语句的功能 SELECT 语句从数据库中返回信息。使用一个 SELECT 语句&#xff0c;可以做下面的事&#xff1a; **列选择&#xff1a;**能够使用 SELECT 语句的列选择功能选择表中的列&#xff0c;这些…...

简易版抽奖活动的设计技术方案

1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来

一、破局&#xff1a;PCB行业的时代之问 在数字经济蓬勃发展的浪潮中&#xff0c;PCB&#xff08;印制电路板&#xff09;作为 “电子产品之母”&#xff0c;其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透&#xff0c;PCB行业面临着前所未有的挑战与机遇。产品迭代…...

1688商品列表API与其他数据源的对接思路

将1688商品列表API与其他数据源对接时&#xff0c;需结合业务场景设计数据流转链路&#xff0c;重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点&#xff1a; 一、核心对接场景与目标 商品数据同步 场景&#xff1a;将1688商品信息…...

高等数学(下)题型笔记(八)空间解析几何与向量代数

目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

在Ubuntu中设置开机自动运行(sudo)指令的指南

在Ubuntu系统中&#xff0c;有时需要在系统启动时自动执行某些命令&#xff0c;特别是需要 sudo权限的指令。为了实现这一功能&#xff0c;可以使用多种方法&#xff0c;包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法&#xff0c;并提供…...

MySQL中【正则表达式】用法

MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现&#xff08;两者等价&#xff09;&#xff0c;用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例&#xff1a; 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...

uniapp中使用aixos 报错

问题&#xff1a; 在uniapp中使用aixos&#xff0c;运行后报如下错误&#xff1a; AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...

USB Over IP专用硬件的5个特点

USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中&#xff0c;从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备&#xff08;如专用硬件设备&#xff09;&#xff0c;从而消除了直接物理连接的需要。USB over IP的…...

Xen Server服务器释放磁盘空间

disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...

解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist

现象&#xff1a; android studio报错&#xff1a; [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决&#xff1a; 不要动CMakeLists.…...