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;}
}
- 进入
coerceReffunction 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 &¤t.ref !== null &&typeof current.ref === 'function' &¤t.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执行getPublicInstanceexport 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ÿ…...
十、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】完全背包总结
本文内容参考了代码随想录,并进行了自己的总结。 完全背包 关键点 ● 每件物品有若干种状态:不选、选 1 件、选 2 件、…、选 n 件 代码 在代码上,只有重量的遍历方向和 01 背包不一样: 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,kubelet和kubectl 定义kubernetes源并指定版本…...
RT-DETR算法优化改进: 下采样系列 | 一种新颖的基于 Haar 小波的下采样HWD,有效涨点系列
💡💡💡本文独家改进:HWD的核心思想是应用Haar小波变换来降低特征图的空间分辨率,同时保留尽可能多的信息,与传统的下采样方法相比,有效降低信息不确定性。 💡💡💡使用方法:代替原始网络的conv,下采样过程中尽可能包括更多信息,从而提升检测精度。 RT-DET…...
CocosCreator3.8源码分析
Cocos Creator架构 Cocos Creator 拥有两套引擎内核,C 内核 和 TypeScript 内核。C 内核用于原生平台,TypeScript 内核用于 Web 和小游戏平台。 在引擎内核之上,是用 TypeScript 编写的引擎框架层,用以统一两套内核的差异…...
(已解决)spingboot 后端发送QQ邮箱验证码
打开QQ邮箱pop3请求服务:(按照QQ邮箱引导操作) 导入依赖(不是maven项目就自己添加jar包): <!-- 邮件发送--><dependency><groupId>org.springframework.boot</groupId><…...
【蓝桥杯冲冲冲】[NOIP2001 普及组] 装箱问题
蓝桥杯备赛 | 洛谷做题打卡day26 文章目录 蓝桥杯备赛 | 洛谷做题打卡day26题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示思路 题解代码我的一些话 [NOIP2001 普及组] 装箱问题 题目描述 有一个箱子容量为 V V V,同时有 n n n 个物品,每…...
2024牛客寒假算法基础集训营1
文章目录 A DFS搜索M牛客老粉才知道的秘密G why外卖E 本题又主要考察了贪心B 关鸡C 按闹分配 今天的牛客,说是都是基础题,头昏昏的,感觉真不会写,只能赛后补题了 A DFS搜索 写的时候刚开始以为还是比较难的,和dfs有关…...
元素的显示与隐藏,精灵图,字体图标,CSSC三角
元素的显示与隐藏 类似网站广告,当我们点击关闭就不见了,但是我们重新刷新页面,会重新出现 本质:让元素在页面中隐藏或者显示出来。 1.display显示隐藏 2.visibility显示隐藏 3.overflow溢出显示隐藏 1.display属性(…...
最新!2024顶级SCI优化!TTAO-CNN-BiGRU-MSA三角拓扑聚合优化、双向GRU融合注意力的多变量回归预测程序!
适用平台:Matlab 2023版及以上 TTOA三角聚合优化算法,将在2024年3月正式发表在中科院1区顶级SCI期刊《Expert Systems with Applications》上。 该算法提出时间极短,目前以及近期内不会有套用这个算法的文献。新年伊始,尽快拿下…...
Flink SQL Client 安装各类 Connector、组件的方法汇总(持续更新中....)
一般来说,在 Flink SQL Client 中使用各种 Connector 只需要该 Connector 及其依赖 Jar 包部署到 ${FLINK_HOME}/lib 下即可。但是对于某些特定的平台,如果 AWS EMR、Cloudera CDP 等产品会有所不同,主要是它们中的某些 Jar 包可能被改写过&a…...
React18-模拟列表数据实现基础表格功能
文章目录 分页功能分页组件有两种接口参数分页类型用户列表参数类型 模拟列表数据分页触发方式实现目录 分页功能 分页组件有两种 table组件自带分页 <TableborderedrowKey"userId"rowSelection{{ type: checkbox }}pagination{{position: [bottomRight],pageSi…...
MySQL查询数据(十)
MySQL查询数据(十) 一、SELECT基本查询 1.1 SELECT语句的功能 SELECT 语句从数据库中返回信息。使用一个 SELECT 语句,可以做下面的事: **列选择:**能够使用 SELECT 语句的列选择功能选择表中的列,这些…...
基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...
2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...
算法:模拟
1.替换所有的问号 1576. 替换所有的问号 - 力扣(LeetCode) 遍历字符串:通过外层循环逐一检查每个字符。遇到 ? 时处理: 内层循环遍历小写字母(a 到 z)。对每个字母检查是否满足: 与…...
RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
FFmpeg:Windows系统小白安装及其使用
一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】,注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录(即exe所在文件夹)加入系统变量…...
【LeetCode】算法详解#6 ---除自身以外数组的乘积
1.题目介绍 给定一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O…...
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...
Python训练营-Day26-函数专题1:函数定义与参数
题目1:计算圆的面积 任务: 编写一个名为 calculate_circle_area 的函数,该函数接收圆的半径 radius 作为参数,并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求:函数接收一个位置参数 radi…...
