【React Hooks原理 - useRef】

概述

在Function Component项目中当我们需要操作dom的时候,第一时间想到的就是使用useRef这个Hook来绑定dom。但是这个仅仅是使用这个Hook而已,为了更好的学习React Hooks内部实现原理,知其所以然。所以本文根据源码从useRef的基础使用场景一步一步到内部实现来对其进行介绍。

基本使用

在React中useRef是这样定义的:useRef保存一个可变的持久化引用,重新渲染时不会重值,更新值也不会渲染页面

export function useRef<T>(initialValue: T): { current: T } {const dispatcher = resolveDispatcher();return dispatcher.useRef(initialValue);
}

由代码能看出useRef接收任意类型的值,包含普通值、函数、dom,然后经过dispather进行派发处理,返回一个包含current属性的对象引用,该对象和普通Js对象一致,更新不收React约束。

一般在项目中useRef常用的有两个使用场景:

  • 通过useRef保持持久化的值,且不需要重新渲染
  • 通过useRef绑定dom,以便直接进行dom操作

比如在项目中常用的定时器,我们都会在组件销毁时通过clear函数进行定时器的清除避免内存泄露等问题,这时候就可以通过useRef来绑定timerId

import { useRef } from 'react';let timerId = useRef(null);useEffect(() => {timerId.current = setInterval(() => {console.log('setInterval');}, 1000);return () => {clearInterval(timerId.current);}
}, [])export default function Counter() {return <></>
}

当我们需要进行dom操作时,比如获取焦点、自动滚动等,就可以通过useRef来绑定dom进行操作

import { useRef } from 'react';let inputRef = useRef(null);useEffect(() => {// 在组件挂载后聚焦输入框inputRef.current.focus();
}, [])export default function Counter() {return <input ref={inputRef} type='text' />
}

源码解析

由于这里的mount、update逻辑很简单,并当useRef传递值/函数和传递dom时的处理是不一样的,所以我们以此来分开介绍。

传递普通值时

当传递普通值时(包含任意类型值、函数),主要执行mountRef、updateRef两个函数。在mount挂载时创建一个包含current属性的对象,然后在更新时返回相同的引用memoizedState保存的,所以这里就在一起写了。

function mountRef<T>(initialValue: T): { current: T } {// 创建hook链表const hook = mountWorkInProgressHook();// ref初始化const ref = { current: initialValue };hook.memoizedState = ref;// 返回refreturn ref;
}function updateRef<T>(initialValue: T): { current: T } {// 复用hookconst hook = updateWorkInProgressHook();// 返回相同引用return hook.memoizedState;
}

从源码能看出,useRef接收一个初始化参数,可以为值/返回值的函数,然后在mountRef中创建了一个包含current的对象,在updateRef中仍然返回的该对象引用。

如果初始值是函数,因为React内部不会做判断,直接将初始值赋予current,如何是函数,则需要手动显式调用

由于不管在mount挂载时,还是在update更新时都是返回的对象引用,以此来保持持久化,当我们通过ref.current修改值时本质修改的是同一个引用对象,所以也不会触发重新渲染(object.is对比一直都是true)。

传递DOM时

当传递DOM时,在mount、update阶段也和传值一样,不会做任何处理会返回相应的对象引用,但是如果传递的是DOM时,在Reconciler协调器中通过React.createElement将JSX转换为React元素后进行fiber构造,在构造完成生产fiber树之后会进入到commit阶段,在该阶段会遍历节点对副作用和ref进行处理,其中在layout阶段会判断当前节点类型(tag)如何是dom(tag === HostComponent)时,如果该dom有ref,则会对ref进行处理commitAttachRef函数

在commit阶段,即renderer阶段,针对dom的不同状态和处理分为了三个阶段: Before Mutation、Mutation、Layout。有兴趣的可以查看这篇文章【React源码 - Fiber架构之Renderer】

以下commitAttachRef代码(省略了部分代码):

function commitAttachRef(finishedWork: Fiber) {// 获取节点的ref属性const ref = finishedWork.ref;if (ref !== null) {// 获取dom实例,fiber.stateNode就是绑定的dom,在completeWork中会创建dom然后绑定到fiber.stateNode上const instance = finishedWork.stateNode;let instanceToUse;switch (finishedWork.tag) {case HostHoistable:case HostSingleton:case HostComponent:// 获取dom实例instanceToUse = getPublicInstance(instance);break;default:instanceToUse = instance;}//if (typeof ref === "function") {// 将dom实例回传给传递的ref函数finishedWork.refCleanup = ref(instanceToUse);} else {// 普通对象赋值到currentref.current = instanceToUse;}}
}

从代码能看出该函数主要就是获取ref绑定的dom实例,然后根据传入ref的不同进行处理,如果是函数则将dom实例传递给函数由开发者显式调用,否则则绑定到current属性上进行返回。

传递函数,显式处理ref的demo:

import React, { useEffect, useRef } from 'react';function App() {const divRef = useRef(null);useEffect(() => {if (divRef.current) {console.log('Element mounted:', divRef.current);}return () => {console.log('Element unmounted:', divRef.current);};}, []);return <div ref={divRef}>Hello, World!</div>;
}export default App;

总结

基于以上了解,我们知道了useRef的基础使用和场景以及背后的代码处理,简要总结一下就是:useRef用于持久化引用,返回普通Js引用,修改其值不会导致组件重新渲染。当传递普通值时,不会进行特殊处理,只是返回相同的对象引用。当绑定dom时,在mount、update阶段初始化对象,然后在commit阶段进行ref处理,函数显式处理则会将dom实例作为参数回传,普通值则会绑定到ref.current中

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/45361.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用shell脚本打印99乘法表

一、简介 前一段时间在旧电脑上安装 antiX 23.1 操作系统&#xff0c;遇到一些问题需要使用shell脚本解决问题&#xff0c;所以专门学习了几天&#xff0c;打印99乘法表是其中的一个练习作业。 二、学习Linux可行的几种方式 虚拟机安装Linux进行学习直接双系统安装在实体电脑…

Ubuntu新系统的使用

1.安装显卡驱动 直接到软件与更新里面&#xff0c;就是一个A字图标的那个软件打开&#xff0c;到附加驱动里选择。要选择“server driver”的&#xff0c;选择后确认即可。 然后输入&#xff1a;nvidia-sim查看 别的方法太复杂&#xff0c;这个方法我亲测了两台电脑&#xff…

kubebuilder入门

1. 安装kubebuilder brew install kubebuilder 2. 需求描述 开发一个zk operator。 cr定义为ZooKeeperCluster 3. 开发过程 3.1 创建一个空的文件夹zk-operator mkdir zk-operator 3.2 进入该文件夹 cd zk-operator 3.3 执行初始化 kubebuilder init --domain my.doma…

MWA(Modern Web App)初学那些事-2-Basic HTML CSS

初学MWA(Modern Web App&#xff09;那些事-2-Basic HTML & CSS 目录 初学MWA(Modern Web App&#xff09;那些事-2-Basic HTML & CSS前言一、本节学习目标二、HTML基础内容2.1关键元素2.4 Scripts 三、CSS 基础内容3.1 级联样式表-用于设置网页样式和布局3.2 CSS规则语…

springcloud使用微服务的搭建

微服务的搭建 1.配置对应信息 Springboot 、springcloud、springcloud alibaba对应关系 https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E 2.pom.xml的配置 2.1 总项目pom.xml引入依赖 <parent><groupId>org.sprin…

阿里通义音频生成大模型 FunAudioLLM 开源

简介 近年来&#xff0c;人工智能&#xff08;AI&#xff09;技术的进步极大地改变了人类与机器的互动方式&#xff0c;特别是在语音处理领域。阿里巴巴通义实验室最近开源了一个名为FunAudioLLM的语音大模型项目&#xff0c;旨在促进人类与大型语言模型&#xff08;LLMs&…

vue3在 setup 中访问路由和当前路由

报错信息&#xff1a; Cannot read properties of undefined (reading $router) 原因&#xff1a; 因为我们在 setup 里面没有访问 this&#xff0c;所以我们不能直接访问 this.$router 或 this.$route。 解决方案&#xff1a; 作为替代&#xff0c;我们使用 useRouter 和…

Oracle字符集修改

提示 Oracle数据库默认的字符集编码为US7ASCII&#xff0c;这个编码是不支持中文的&#xff0c;如果想要在数据库存储中文&#xff0c;就需要修改编码为ZHS16GBK或UTF-8 编码和字符集是一个意思&#xff0c;只是叫法不一样而已 前置条件 修改字符集的前提是知道我们现在用的是什…

跳妹儿学编程之ScratchJr(9):程序控制积木篇—短跑比赛

跳妹儿学编程之ScratchJr(7)&#xff1a;动作积木篇—爸爸去散步 跳妹儿学编程之ScratchJr(8)&#xff1a;外观积木篇—捉迷藏 跳妹儿学编程之ScratchJr(9)&#xff1a;程序控制积木篇—短跑比赛 引言 在之前的一篇文章中&#xff0c;我们了解了ScratchJr的动作积木和外观积…

std::getline

std::getline 是 C 标准库中的一个函数&#xff0c;用于从输入流中读取一行数据并存储到字符串中。它通常用于读取用户输入或从文件中读取文本数据。以下是 std::getline 的一般用法和说明&#xff1a; #include <iostream> #include <string>int main() {std::st…

skywalking 请求链路采样设置和原理

目标 skywalking 默认情况会采集大量 trace 数据&#xff0c;这样可以比较全的追踪所有请求调用链路的请求&#xff0c;但同时对 ES 存储资源要求非常高&#xff0c;需要我们投入很大的存储节点才可以。那么有没有一种采样的请求上报的机制呢&#xff1f;答案是有的&#xff0…

阿里云ECS服务器安装jdk并运行jar包,访问成功详解

安装 OpenJDK 8 使用 yum 包管理器安装 OpenJDK 8 sudo yum install -y java-1.8.0-openjdk-devel 验证安装 安装完成后&#xff0c;验证 JDK 是否安装成功&#xff1a; java -version设置 JAVA_HOME 环境变量&#xff1a; 为了确保系统中的其他应用程序可以找到 JDK&…

星火智能体创建指南,星火大模型智能体创建教程

一、什么是星火助手 星火助手是基于讯飞星火认知大模型&#xff0c;面向用户使用场景&#xff0c;打造的高效生产力工具。通过设置结构化的指令模板&#xff0c;用户即可完成助手功能设定&#xff0c;每个助手在对话的模式下能够快速满足场景需求。同时支持助手模板、数据集、…

Spring boot 2.0 升级到 3.3.1 的相关问题 (一)

文章目录 Spring boot 2.0 升级到 3.3.1 的相关问题 &#xff08;一&#xff09;拦截器Interceptor的变动问题介绍解决方案 WebMvcConfigurerAdapter 自定义Mvc配置问题介绍解决方案 Spring boot 2.0 升级到 3.3.1 的相关问题 &#xff08;一&#xff09; 拦截器Interceptor的…

单链表算法 - 链表的中间节点

. - 力扣&#xff08;LeetCode&#xff09;. - 备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/middle-of-the-linked-list/description/ 思路1: 思路2: 代码: /*** …

【接口自动化_06课_Pytest+Excel+Allure完整框架集成】

一、logging在接口自动化里的应用 1、设置日志的配置&#xff0c;并收集日志文件 日志的设置需要在pytest.ini文件里设置。这个里面尽量不要有中文 2、debug日志的打印 pytest.ini文件的开关一定得是true才能在控制台打印日志 import allure import pytest from P06_PytestFr…

CUDA cuDNN和pytorch(GPU版)的完整安装教程

​ * 说明: 本教程使用wsl-ubuntu20.04, 其他发行版linux的命令可能有所区别. *实测机型: i5-13500HX | RTX 4060 Laptop 一、下载CUDA12.X版本 这里以下载CUDA12.2为例。 前往cuda-12.2下载页, 按照如图方式选择合适的选项&#xff1a; 按照官方给出的命令&#xff0c; 在b…

Trie树的应用

Trie树的应用 题目解题思路代码 题目 维护一个字符串集合&#xff0c;支持两种操作&#xff1a; I x 向集合中插入一个字符串 x x x&#xff1b;Q x 询问一个字符串在集合中出现了多少次。 共有 N N N 个操作&#xff0c;所有输入的字符串总长度不超过 1 0 5 10^5 105&am…

ArkTS学习笔记_封装复用之@builderParam装饰器

ArkTS学习笔记_封装复用之builderParam装饰器 作用&#xff1a; 在自定义组件中&#xff0c;该装饰器用于装饰函数成员变量&#xff0c;builderParam装饰的函数成员变量的值必须是经过builder装饰的方法。变量初始化后可以在自定义组件内调用。初始化&#xff1a; 可以使用自定…

移动应用性能关注分析哪些指标

移动应用常见性能指标 要对应用开展性能测试&#xff0c;首先需要了解需要重点关注哪些指标&#xff1f;指标的参考范围大致是多少&#xff1f;可采用哪些工具收集这些指标&#xff1f;如何收集&#xff1f;如果指标有异常&#xff0c;大致有哪些high level的优化思路。这篇博客…