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

中科蓝讯蓝牙耳机SDK实战:从目录解析到消息处理框架 本文深入解析中科蓝讯蓝牙耳机SDK的目录结构与消息处理框架,提供从硬件驱动到蓝牙功能定制的实战指南。重点剖析SDK核心模块如消息队列机制、按键事件处理及电量报告优化,并分享二次开发中的调试技巧与避坑经验,助力开发者高效完成TWS耳机功能定制。 阅读详情

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 就等于 owner
          if (owner) {
            const ownerFiber = ((owner: any): Fiber);
            invariant(
              ownerFiber.tag === ClassComponent,
              'Function components cannot have refs.',
            );
            // 然后它的 inst 就是 ownerFiber.stateNode
            // 也就是我们 class component,那个 instance 也就是 this
            inst = 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 从之前挂载的地方去给它卸载下来,看下这个  commitDetachRef
    if (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: {
        // 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.
        nextEffect.effectTag &= ~Placement;

        // Update
        const 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应用更新的过程当中,如何被实现的原理
U盘安装单系统ubuntu10.04之全过程&拔掉U盘后系统无法启动 借助U盘把电脑装成ubuntu单系统之过程记录,以及遇到拔掉U盘后系统无法启动问题,电脑就显示: Non-System disk or disk error, replace and strike any key when ready 阅读详情

相关推荐

Scrapy实现微博关键词爬虫(爬虫结果写入mongodb)

话不多说,上代码: spiders文件夹下的microBlogSpider.py里这样写: # -*- coding: utf-8 -*- import scrapy from scrapy import Spider, Request, FormRequest from scrapy.selector import Selector import datetime import random from blogSpider.items import microBlogItem, keyWordItem f

qq_39360565的博客 1780

react16:使用ref

文章目录ref是一个字符串ref是一个回调函数回调函数是内联形式回调函数绑定在类实例上createRef不要过度使用ref ref是一个字符串 react官网不推荐使用字符串形式的ref,它可能在未来的某个版本被移除。 <input type="text" ref="input1"/>, <input type="text" ref="input2"/>, 组件实例的refs属性上以key-value的形式保存着真实DOM元素。 <!DOCTYPE html> <

richest_qi的博客 982

【阅读文献笔记】FoundationStereo: Zero-Shot Stereo Matching

提出首个零样本泛化立体匹配基础模型设计 STA 模块融合单目先验提出 AHCF 模块增强代价滤波构建 100 万合成数据集 FSD + 自净化流程。

qq_46460379的博客 1258

react16——ref & dom

基本使用 1.Refs 提供了一种方式,用于访问在 render 方法中创建的 DOM 节点或 React 元素。 2.使用refs的情况: (1)处理焦点、文本选择、媒体控制。 (2)自定义动画 (3)通过第三方DOM库,获取DOM节点 3.如果可以,优先通过声明状态实现,进而避免使用refs。 例如,不要在 Dialog 组件上直接暴露 open() 和 close() 方法,最好传递 isO...

abc的博客 1507

NeRF必读:Mip-NeRF总结与公式推导

由于远景近景的分辨率不同,导致经典NeRF对于多尺度场景的表达存在明显瑕疵:NeRF对于近景的重建比较模糊而对于远景的重建出现锯齿。简单粗暴的策略是supersampling,但是费时费力。相较于NeRF使用的位置编码(PE)方式,Mip-NeRF提出了积分位置编码的方式(IPE).这种编码方式可以多尺度地描述空间中的信息分布,make sense.

i_head_no_back的博客 6080

Xshell 连接 kali 虚拟机教程

启动ssh服务,启动后重新查看ssh服务状态,如下图active(running)正常。至此便可以直接使用Xshell登录kali系统了。至此虚拟机已经可以上网。-查看ssh服务状态。

qq_40726937的博客 2219

react16中ref的使用

在 React16 新版本中,新引入了 React.createRef 与 React.forwardRef 两个 API,有计划移除老的 string ref,使 ref 的使用更加便捷与明确。如果你的应用已经升级到 React16.3+ 版本,那就放心大胆使用 React.createRef 吧,如果暂时没有的话,建议使用 callback ref 来代替 string ref。 strin...

马优晨 6907

用OpenNI2获取奥比中光Astra Pro及 Astra Pro SM输出的深度图(win,linux arm64 x64平台)

搞了一个奥比中光Astra Pro,想在windows平台,和linux rk3588 上获取深度信息,之前的驱动下载已经不好用了。

babytiger的专栏 1308

react 学习 —— 16、使用 ref 操作 DOM

有时你可能需要访问由 React 管理的 DOM 元素 —— 例如,让一个节点获得焦点、滚动到它或测量它的尺寸和位置。在 React 中没有内置的方法来做这些事情,所以你需要一个指向 DOM 节点的 ref 来实现。

TE杨 1841

基于sklearn实现LDA主题模型(附实战案例)

目录LDA主题模型1.LDA主题模型原理2.LDA主题模型推演过程3.sklearn实现LDA主题模型(实战)3.1数据集介绍3.2导入数据3.3分词处理 3.4文本向量化3.5构建LDA模型3.6LDA模型可视化 3.7困惑度 其实说到LDA能想到的有两个含义,一种是线性判别分析(Linear Discriminant Analysis),一种说的是概率主题模型:隐含狄利克雷分布(Latent Dirichlet Allocation,简称LDA)。 现在讨论的是主题模型

m0_64336780的博客 2万+

第11节:GPU 线程层级:线程、线程块、网格

层级标识变量功能线程threadIdx执行单元,处理数据线程块blockIdx管理多个线程,提供共享内存网格管理多个 blockGPU 编程中,合理安排线程层级,是提升性能的关键。

yumeiguo的博客 212

React.16版本当中ref的使用方式

Ref String(最不被推荐的一种方式,即将被废弃) class test extends Component { render() { return ( <div> <span ref="StringRef">content</span> </div> ); } compone...

qiguoqi777的博客 867

RK3568移植AP6256

本文主要是记录RK3568移植正基的AP6256的WIFIBT模块,感兴趣的朋友可以一起学习!!!提示:以下是本篇文章正文内容,下面案例可供参考。

qq_24093081的博客 3716

【React源码16】深入学习React 源码实现—— Hooks闭包陷阱问题

React Hooks中的闭包陷阱是指异步回调函数捕获的是旧的state或props值,而非最新值。这种现象在useEffect、setTimeout、setInterval等场景中尤为常见。

silverclayz的技术分享 1854

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

React16源码: createRef与forwardRef源码实现

拿一个场景举例开始比如,在react当中写一个组件,类型是 class Component在 render function 里面渲染一系列的子组件或者 dom节点会有这样的需求,就是获取某个dom节点或某个子组件的实例来做一些手动的操作不局限于 props 更新这种方式来更新这个节点,获取节点后,可以绑定某些事件做一些操作如果没有其他好方法,可能要自己去写之类的选择器来获取这个 dom 节点在 react 当中就为我们提供了这种方式。

Wang的专栏 1443

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

从源码到应用:深入理解react-merge-refs的工作机制

react-merge-refs是一个轻量级的React工具库,专门用于合并多个Ref引用。在React开发中,Refs是访问DOM元素或组件实例的重要方式,而当我们需要同时管理多个Ref时,react-merge-refs提供了简单高效的解决方案。本文将从源码结构到实际应用,全面解析这个实用工具的工作原理和使用方法。 ## 为什么需要合并Refs? 在React组件开发中,我们经常会遇到需要

gitblog_00649的博客 675

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

【React源码24】深入学习React 源码实现——useRef底层实现

React的useRef Hook实现原理分析 摘要:本文深入分析了React中useRef Hook的实现原理。useRef用于创建可变的引用对象,其核心特性包括:跨渲染周期保持引用、不触发重新渲染、可用于保存DOM引用或任意值。源码分析显示,React通过Hook链表结构管理状态,首次渲染时创建ref对象并存入memoizedState,后续渲染直接返回缓存的对象。简化实现示例展示了如何通过全局索引和数组来模拟Hook的状态管理机制。useRef的设计体现了React函数组件中持久化状态的解决方案。

silverclayz的技术分享 870

React16源码: React中commitAllHostEffects内部的commitDeletion的源码实现

对于 Portal,ClassComponent,还有 HostComponent,会有不同的操作。对于这个整体流程,用下面的图来看下,比如说,要删除图中 App下的 div 节点。最重要的就是理解这个算法它如何进行递归的调用来遍历整棵子树每一个节点的过程。需要注意的是,对于HostComponent的子树的遍历会放到这个。方法,它遍历的过程的目的是。描述了整个删除的流程。

Wang的专栏 1364

react引入流程图_YCK 的 React 源码解读 —— 先热个身

这是我的 React 源码解读课的第一篇文章,首先来说说为啥要写这个系列文章:现在工作中基本都用 React 了,由此想了解下内部原理市面上 Vue 的源码解读数不胜数,但是反观 React 相关的却寥寥无几,也是因为 React 源码难度较高,因此我想来攻克这个难题自己觉得看懂并不一定看懂了,写出来让读者看懂才是真懂了,因此我要把我读懂的东西写出来 这个系列文章预计篇数会超过十篇,React 版...

weixin_42562079的博客 253

react-merge-refs常见问题解答:新手必知的8个要点

react-merge-refs是一个实用的React工具库,专门用于合并多个ref引用。对于React开发者来说,处理组件引用是日常开发中经常遇到的需求,而这个轻量级工具能够帮助你轻松解决多个ref共存的问题。本文将解答使用react-merge-refs时的8个常见问题,帮助新手快速掌握这个工具的使用技巧。 ## 一、什么是react-merge-refs? react-merge-ref

gitblog_00053的博客 538

【React源码28】深入学习React 源码实现—— useImperativeHandle底层实现(最完整版)

本文深入解析 React 中的 useImperativeHandle Hook,它是配合 forwardRef 使用的关键 API,允许函数组件自定义暴露给父组件的实例值。文章从历史背景出发,对比类组件和函数组件的 ref 处理差异,详细解析了其源码实现位置(主要在 ReactFiberHooks.js 等文件)和语法结构。核心算法部分通过代码示例展示了初始化流程,包括创建暴露对象、赋值 ref.current 以及标记副作用等关键步骤。该 Hook 解决了函数组件没有实例的问题,使父组件能安全调用子组件

silverclayz的技术分享 1190

react源码浅析

项目地址

weixin_38405133的博客 327

【夯实基础】react16-学习笔记

Facebook起草的JS扩展语法本质是一个JS对象,会被babel编译,最终会被转换为React.createElement每个JSX表达式,有且仅有一个根节点每个JSX元素必须结束(XML规范)

aXin_li的博客 1487

react 展示对象_剖析 React 源码:先热个身

这是我的 React 源码解读课的第一篇文章,首先来说说为啥要写这个系列文章:现在工作中基本都用 React 了,由此想了解下内部原理市面上 Vue 的源码解读数不胜数,但是反观 React 相关的却寥寥无几,也是因为 React 源码难度较高,因此我想来攻克这个难题自己觉得看懂并不一定看懂了,写出来让读者看懂才是真懂了,因此我要把我读懂的东西写出来这个系列文章预计篇数会超过十篇,React 版本...

weixin_42231590的博客 158

React源码解析-React 导出了些什么

React 用了好久,是不是发现自己依然不懂 React 实现原理?只知道它的基本用法,弄不懂 React 内部流程? 本系列文章基于 React v16.8.6,每周分享2-3篇精华文章,每篇文章都是 React 中的一个关键点。欢迎评论交流。 React 导出的对象详解 直接复制的代码,在代码上面打的注释 var React = { // -----------------------...

止水聊技术 350

【React源码学习笔记】学习计划安排

这篇React源码学习指南提供了8周的系统学习路线,覆盖React核心机制的深度解析。从Fiber架构、协调算法、调度器到Hooks实现原理,分阶段掌握React内部工作原理。包含环境搭建、源码路径、关键概念解析和实战项目(手写Mini-React),配合推荐资源和学习笔记,帮助开发者由浅入深理解React底层实现。适合想深入React技术细节的中高级开发者。

silverclayz的技术分享 1009
上一篇: React16源码: React中NewContext的源码实现
下一篇: React16源码: React中处理hydrate的核心流程源码实现
Wang's Blog
博客等级 码龄12年 7438粉丝 2421原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值