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 语句的列选择功能选择表中的列,这些…...
铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...
Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...
