Vue 人看 React useRef:它不只是替代 ref

news2025/4/21 0:18:49

如果你是从 Vue 转到 React 的开发者,初见 useRef 可能会想:这不就是 React 版的 ref 吗?但真相是 —— 它能做的,比你想象得多得多。

👀 Vue 人初见 useRef

在 Vue 中,ref 是我们访问 DOM 或响应式数据的利器。但在 React 中,useRef 并不止是一个获取 DOM 的工具,它更像是一个“不会引起重新渲染的变量容器”。

如果你在想:

  • “为啥我改了 ref.current,界面却没更新?”
  • “这玩意儿跟 Vue 的 ref 好像不太一样?”
  • “它除了操作 DOM 还能干嘛?”

这篇文章,就帮你用 Vue 人的视角,彻底搞懂 useRef 的多种用法与常见陷阱。


🔍 什么是 useRef

引入 React 文档的话:

useRef 是一个 React Hook,它能帮助引用一个不需要渲染的值”

useRef 创建的是一个普通的 Javascript 对象,里面仅有一个 current 属性,用于读取和修改。

import { useRef } from "react";

function Example() {
  const countRef = useRef(0);
  countRef.current += 1;
}

useRef 是一个 可变的盒子,你可以把任何值塞进去,它不会重新触发组件 render,但你可以随时取用。

🧪 举个栗子:
export default function Example() {
  const countRef = useRef(0);
  console.log("render");

  return (
    <button onClick={() => (countRef.current += 1)}>
      点击count:{countRef.current}
    </button>
  );
}

我们创建一个按钮去给 countRef 进行自增,我们看看组件有没有重新 render

在这里插入图片描述

可以看到,虽然 countRef 数据自增了,但是却不会 触发新的渲染

“当你希望组件“记住”某些信息,但又不想让这些信息 触发新的渲染 时,你可以使用 useRef

如果你比较熟悉 useState,我们可以举个更简单的例子:

import { useState } from "react";

export default function Example() {
  const [countRef, never] = useState({ current: 0 });

  return (
    <button onClick={() => (countRef.current += 1)}>
      点击count:{countRef.current}
    </button>
  );
}

原则上 useRef 可以在 useState 的基础上实现:

不使用setup函数去改变值,不去触发新的渲染。

了解了基本概念,我们再来看看它与 Vue 的 ref 有哪些关键不同。

⚔️ useRef 和 Vue ref 的区别

对比点useRefVue ref
响应性不具备响应性,不触发 render具备响应性,数据变化会更新视图
使用场景DOM引用、缓存变量、定时器、历史值等DOM引用、响应式数据
数据结构{ current: value }value 是响应式对象
是否引发视图更新

Vue 的 ref响应式容器,值变化会自动更新视图;

而 React 的 useRef 更像一个可读写但不具响应性的变量盒子。

🧩 useRef 常见使用场景

前面介绍完 useRef 的基本概念和使用方法,我们接下来看看平时开发中比较常见的使用场景:

1. 定时器引用

我们来实现一个 简易计时器

import { useState, useRef } from "react";

export default function Example() {
  const [time, setTime] = useState(0);
  const timerRef = useRef(null);

  const handleStart = () => {
    if (timerRef.current) return;
    const startTime = Date.now();
    timerRef.current = setInterval(() => {
      setTime(Date.now() - startTime);
    }, 10);
  };
  const handleStop = () => {
    if (!timerRef.current) return;
    clearInterval(timerRef.current);
    timerRef.current = null;
    console.log("销毁定时器:", timerRef.current);
  };

  return (
    <>
      <h1>计时器: {time}</h1>
      <button onClick={handleStart}>开始</button>
      <button onClick={handleStop}>停止</button>
    </>
  );
}
  • 按下开始键,计时器 开始进行计时,这时候把定时器存到 timerRef
  • 按下停止键,销毁当前定时器,防止出现 闭包导致的内存泄漏

在这里插入图片描述

2. 操作 DOM

我们假定一个场景,用户进入页面时,我们需要用户光标默认 聚焦到输入框

import { useEffect, useRef } from "react";

export default function Example() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current?.focus();
  }, []);

  return <input ref={inputRef} />;
}

我们需要:

  • 使用 useRef 创建 inputRef,默认值为 null
  • 使用 ref={inputRef} 去存储当前 DOM 元素。
  • 通过 useEffect 在进入页面时进行 inputRef.current?.focus()

这里类似 Vue 的 ref="xxx" + this.$refs.xxx.focus()

注意: 不要在渲染过程中读取或写入ref.current,会使ref变得不可预测。

在这里插入图片描述

使用 ref 去存储正常的标签都能正常获取其 DOM 元素,但是当你尝试将 ref 放在 自定义组件 上,会发生什么呢?

3. 绑定自定义组件的 ref

我们先来实践一下:

import { useEffect, useRef } from "react";

function MyInput(props) {
  return <input {...props} />;
}

export default function Example() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current?.focus();
  }, []);

  return <MyInput ref={inputRef} />;
}

我们创建一个 MyInput 子组件,然后把 ref 绑定到我们子组件上。

控制台直接给我们弹了报错:

在这里插入图片描述

React 向控制台打印一条错误消息,提示我们如果想操控子组件,需要去使用 forwardRef API。

forwardRef 的用法

  • forwardRef表示允许子组件将其 DOM 节点放入 ref 中,默认情况下是不允许的。
  • forwardRef会让传入的子组件多一个 ref 作为第二个参数传入,用于存储当前 DOM 节点,第一个参数为 props

我们改进下:

const MyInput = forwardRef((props, ref) => {
  return <input {...props} ref={ref} />;
});

我们将 ref 绑定到 MyInput 组件中的 input,再来看看效果:

在这里插入图片描述

现在不会报错了,并且 input 框也正常 聚焦 了。


总结

  • useRef 用于存储值且不想去触发 render 的场景。
  • useRef 创建的值通过 .current 去进行 读取、修改
  • 常见用于存储 定时器、 DOM 节点
  • 存储自定义组件的 DOM 节点需配合 forwardRef API 使用。
  • 需要注意 不要在渲染过程中读取或写入 ref.current

如果你也是从 Vue 转过来的,看到这里可能已经对 useRef 有了更清晰的认知 —— 它并不是 Vue 的 ref 替代品,而是一种完全不同思路下的状态管理补充工具

希望这篇文章能帮你快速掌握 useRef,如果你觉得有帮助,别忘了点个赞👍或关注我后续的 重学 React 系列!

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

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

相关文章

零成本自建企业级SD-WAN!用Panabit手搓iWAN实战

我们前面提到过&#xff0c;最开始了解到Panabit&#xff0c;是因为他的SD-WAN产品&#xff08;误以为是外国货&#xff1f;这家国产SD-WAN神器竟能免费白嫖&#xff0c;附Panabit免费版体验全记录&#xff09;&#xff1b;现在发现&#xff0c;其SD-WAN的技术基础是iWAN&#…

Unity-微信截图功能简单复刻-03绘制空心矩形

思路-绘制空心矩形 拓展UGUI的Graphic类,实现拖拽接口。 开始拖拽时记录鼠标位置&#xff0c; 使用拖拽中的鼠标位置和记录的位置&#xff0c;计算矩形顶点&#xff0c;绘制矩形。 两个三角形合并为一个矩形&#xff0c;作为空心矩形的一条边&#xff0c;四个边合并为空心矩形…

国产品牌芯洲科技100V降压芯片系列

SCT2A25采用带集成环路补偿的恒导通时间(COT)模式控制&#xff0c;大大简化了转换器的片外配置。SCT2A25具有典型的140uA低静态电流&#xff0c;采用脉冲频率调制(PFM)模式&#xff0c;它使转换器在轻载或空载条件下实现高转换效率。 芯洲科技100V降压芯片系列提供丰富的48V系…

研一自救指南 - 07. CSS面向面试学习

最近的前端面试多多少少都会遇到css的提问&#xff0c;感觉还是要把重点内容记记背背。这里基于b站和我自己面试的情况整理。 20250418更新&#xff1a; 1. BFC Block Formatting Context&#xff0c;一个块级的盒子&#xff0c;可以创建多个。里面有很多个块&#xff0c;他们…

图灵奖得主LeCun:DeepSeek开源在产品层是一种竞争,但在基础方法层更像是一种合作;新一代AI将情感化

图片来源&#xff1a;This is World 来源 | Z Potential Z Highlights&#xff1a; 新型的AI系统是以深度学习为基础&#xff0c;能够理解物理世界并且拥有记忆、推理和规划能力的。一旦成功构建这样的系统&#xff0c;它们可能会有类似情感的反应&#xff0c;但这些情感是基…

使用Redis5.X部署一个集群

文章目录 1.用Redis5.x来创建Cluste2. 查看节点信息 nodes3. 添加节点 add-node4.删除节点 del-node5.手动指定从节点 replicate6.检查集群健康状态 check 建议使用5.x版本。 首先&#xff0c;下载Redis&#xff0c;根据自己的环境选择版本。 一键启动Redis集群文件配置。 ech…

Ubuntu Linux 中文输入法默认使用英文标点

ubuntu从wayland切换到x11, 然后安装fcitx(是fcitx4版本)和 fcitx-googlepinyin, 再sudo dpkg -i 安装百度输入法deb包. 在fcitx配置中, 附加组件,打勾高级, 取消打勾标点支持和全角字符. 百度输入法就可以默认用英文标点了. 而google拼音输入法的问题是字体大小没法保存,每…

Java漏洞原理与实战

一、基本概念 1、序列化与反序列化 (1)序列化:将对象写入IO流中&#xff0c;ObjectOutputStream类的writeobject()方法可以实现序列化 (2)反序列化:从IO流中恢复对象&#xff0c;ObjectinputStream类的readObject()方法用于反序列化 (3)意义:序列化机制允许将实现序列化的J…

第十届团体程序设计天梯赛-上理赛点随笔

2025.4.19来到军工路580号上海理工大学赛点参加cccc 校内环境挺好的&#xff0c;校内氛围也不错&#xff1b;临走前还用晚餐券顺走一袋橘子 再来说说比赛 首先是举办方服务器爆了&#xff0c;导致前10分钟刷不出题&#xff0c;一个多小时还上交不了代码 然后就是我用py总有几…

面试专栏-02-MySQL知识点(第二部分)

6、锁 1、分类&#xff1a; 全局锁&#xff1a;锁住数据库中的所有表表级锁&#xff1a;每次操作锁住整张表行级锁&#xff1a;每次操作锁住对应行的数据 2、全局锁 加锁后&#xff0c;整个实例只能进行读取操作&#xff0c;从而保证数据的完成性和一致性。 特点&#xff…

【MySQL数据库入门到精通】

文章目录 一、SQL分类二、DDL-数据库操作1.查询2.创建数据库3.删除数据库4.使用数据库 三、DDL-表操作1.查询 一、SQL分类 根据功能主要分为DDL DML DQL DCL DDL:Date Definition Language数据定义语言&#xff1a;定义数据库&#xff0c;表和字段 DML:Date Manipulatin Lan…

[Swift]pod install成功后运行项目报错问题error: Sandbox: bash(84760) deny(1)

操作&#xff1a; platform :ios, 14.0target ZKMKAPP do# Comment the next line if you dont want to use dynamic frameworksuse_frameworks!# Pods for ZKMKAPPpod Moyaend pod install成功后运行报错 报错&#xff1a; error: Sandbox: bash(84760) deny(1) file-writ…

游戏引擎学习第233天

原地归并排序地方很蒙圈 game_render_group.cpp&#xff1a;注意当前的SortEntries函数是O(n^2)&#xff0c;并引入一个提前退出的条件 其实我们不太讨论这些话题&#xff0c;因为我并没有深入研究过计算机科学&#xff0c;所以我也没有太多内容可以分享。但希望在过去几天里…

卷积神经网络基础(二)

停更好久的卷积神经网络基础知识终于开始更新了哈哈&#xff0c;今天主要介绍的是误差反向传播法。 目录 一、计算图 1.1 用计算图求解 1.2 局部计算 1.3 为什么采用计算图 二、链式法则 2.1 计算图的反向传播 2.2 链式法则 2.3 链式法则和计算图 三、反向传播 3.1 …

探索大语言模型(LLM):定义、发展、构建与应用

文章目录 引言大规模语言模型的基本概念大规模语言模型的发展历程1. 基础模型阶段&#xff08;2018年至2021年&#xff09;2. 能力探索阶段&#xff08;2019年至2022年&#xff09;3. 突破发展阶段&#xff08;以2022年11月ChatGPT的发布为起点&#xff09; 大规模语言模型的构…

树莓派超全系列教程文档--(33)树莓派启动选项

树莓派启动选项 启动选项start_file &#xff0c;fixup_filecmdlinekernelarm_64bitramfsfileramfsaddrinitramfsauto_initramfsdisable_poe_fandisable_splashenable_uartforce_eeprom_reados_prefixotg_mode &#xff08;仅限Raspberry Pi 4&#xff09;overlay_prefix配置属…

Python 爬虫解决 GBK乱码问题

文章目录 前言爬取初尝试与乱码问题编码知识科普UTF - 8GBKUnicode Python中的编码转换其他编码补充知识GBKGB18030GB2312UTF&#xff08;UCS Transfer Format&#xff09;Unicode 总结 前言 在Python爬虫的过程中&#xff0c;我尝试爬取一本小说&#xff0c;遇到GBK乱码问题&a…

解决echarts饼图label显示不全的问题

解决办法 添加如下配置&#xff1a; labelLayout: {hideOverlap: false},

JCST 2025年 区块链论文 录用汇总

Conference&#xff1a;Journal of Computer Science and Technology (JCST) CCF level&#xff1a;CCF B Categories&#xff1a;交叉/综合/新兴 Year&#xff1a;2025&#xff08;截止4.19&#xff09; JCST 2024年 区块链论文 录用汇总 1 Title: An Understandable Cro…

不带无线网卡的Linux开发板上网方法

I.MX6ULL通过网线上网 设置WLAN共享修改开发板的IP 在使用I.MX6ULL-MINI开发板学习Linux的时候&#xff0c;有时需要更新或者下载一些资源包&#xff0c;但是开发板本身是不带无线网卡或者WIFI芯片的&#xff0c;尝试使用网口连接笔记本&#xff0c;笔记本通过无线网卡连接WIFI…