useRef :掌握 DOM 访问与持久化状态的利器

一、为何需要 useRef?

在 React 开发中,我们经常需要处理两种核心问题:如何访问 DOM 元素如何存储不随渲染更新的变量 。虽然 useStateuseEffect 是大多数场景的首选工具,但它们并不适用于所有需求。

比如,你可能遇到这样的场景:

  • 用户填写表单后,点击"提交"按钮时自动聚焦到某个输入框。
  • 在组件中设置一个计时器,但关闭组件时需要清除计时器。
  • 需要记录组件上一次的某个状态值(比如上一次的 propsstate),以便与当前值进行比较。

这时候,useStateuseEffect 可能无法直接解决问题,而 useRef 就成了我们的"秘密武器"。接下来,我将通过这篇文章,带你了解useRef的使用。


二、useRef 的概念及核心特性

什么是 useRef?

useRef 是 React 提供的一个 Hook,用于创建一个可变的引用对象 ,这个对象的 .current 属性可以指向任何值(如 DOM 元素、数字、字符串等),并且在组件的整个生命周期内保持不变。

你可以把它想象成一个"抽屉"或"保险箱":

  • 你可以随时往里面放东西(比如 DOM 元素、计时器 ID),也可以随时取出。
  • 即使组件重新渲染,里面的内容也不会被清空,而是始终保持不变。

核心特性

  1. 访问 DOM 元素
    通过 ref 直接操作 HTML 元素,比如获取输入框的值、触发聚焦、滚动到特定位置等。
  2. 保持值不触发重渲染
    修改 .current 的值时,不会触发组件重新渲染。这很适合存储不需要驱动 UI 更新的数据。
  3. 与函数组件协同工作
    在函数组件中,useRef 可以像类组件的 this 一样保存状态。比如,在类组件中,我们可能会用 this.timerId 存储计时器 ID,而在函数组件中,useRef 就是这个角色的完美替代者。

三、useRef 的使用方法

1. 创建 ref 对象并绑定 DOM 元素

javascript 复制代码
Jsx
深色版本
import React, { useRef } from 'react';

function TextInput() {
  const inputRef = useRef(null);

  const focusInput = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>聚焦输入框</button>
    </div>
  );
}

语法与特性解析

  • const inputRef = useRef(null);
    这是 useRef 的基本语法:useRef(initialValue)。它接收一个初始值(这里是 null),并返回一个对象,该对象具有一个 .current 属性。这个返回的对象在组件的整个生命周期中是恒定不变的(即每次渲染都指向同一个对象)。
  • <input ref={inputRef} />
    这里将 useRef 创建的 inputRef 对象通过 ref 属性绑定到 <input> 元素上。React 会在组件挂载时自动将该 DOM 元素的引用赋值给 inputRef.current。当组件卸载时,inputRef.current 会被设为 null
  • inputRef.current.focus();
    在事件处理函数中,我们通过访问 inputRef.current 来获取对 DOM 元素的引用,并调用其原生方法 focus()关键点useRef 提供了一种安全、直接的方式访问 DOM,而不会触发组件重新渲染。

特性总结useRef 创建的 ref 对象是一个"稳定"的引用,其 .current 属性可以被随时读取和修改,且修改它不会引起组件重新渲染


2. 保存不随渲染更新的值

ini 复制代码
Jsx
深色版本
import React, { useRef, useEffect } from 'react';

function Counter() {
  const countRef = useRef(0);
  const [count, setCount] = React.useState(0);

  useEffect(() => {
    countRef.current = count;
  }, [count]);

  const increment = () => {
    setCount(prev => prev + 1);
    console.log('当前计数(来自 ref):', countRef.current);
  };

  return (
    <div>
      <p>当前计数: {count}</p>
      <button onClick={increment}>+1</button>
    </div>
  );
}

语法与特性解析

  • const countRef = useRef(0);
    我们创建了一个 ref 对象 countRef,初始值为 0。与 useState 不同,useRef 的初始值只在组件首次渲染时被使用,后续渲染中不会重新创建。
  • countRef.current = count;
    useEffect 中,我们将当前的 count 值同步到 countRef.current。这里的关键是:修改 countRef.current 不会触发组件重新渲染 。这使得 useRef 成为存储"副作用"或"临时状态"的理想选择。
  • console.log(countRef.current);
    increment 函数中,我们读取 countRef.current。注意,此时 countRef.current 保存的是上一次渲染时的 count 值(因为 useEffect 是在渲染后异步执行的)。这展示了 useRef 如何帮助我们访问"上一个"状态。

特性总结useRef 可以用来存储任何可变值,并且这个值在组件的整个生命周期中保持不变,修改它不会触发重渲染,非常适合存储不需要驱动 UI 更新的数据。


3. 避免重复计算与副作用清理

javascript 复制代码
Jsx
深色版本
import React, { useRef, useState } from 'react';

function Timer() {
  const intervalRef = useRef(null);

  const startTimer = () => {
    intervalRef.current = setInterval(() => {
      console.log('计时器触发');
    }, 1000);
  };

  const stopTimer = () => {
    clearInterval(intervalRef.current);
  };

  return (
    <div>
      <button onClick={startTimer}>开始计时</button>
      <button onClick={stopTimer}>停止计时</button>
    </div>
  );
}

语法与特性解析

  • const intervalRef = useRef(null);
    我们创建了一个 ref 对象来存储计时器的 ID。这个 ID 是一个数字,但 useRef 可以存储任何类型的值。
  • intervalRef.current = setInterval(...);
    当点击"开始计时"按钮时,setInterval 返回一个计时器 ID,我们将其赋值给 intervalRef.current。由于 useRef 的稳定性,这个 ID 会被安全地保存下来,即使组件重新渲染也不会丢失。
  • clearInterval(intervalRef.current);
    点击"停止计时"时,我们通过 intervalRef.current 获取之前保存的计时器 ID,并调用 clearInterval 来清除计时器。如果没有 useRef,我们可能无法在多个事件处理函数之间共享这个 ID

特性总结useRef 是管理副作用(如计时器、订阅、DOM 操作)的理想工具,因为它提供了一个持久的"容器"来存储这些副作用的引用,确保它们可以在组件的不同部分被安全地访问和清理。


结语:

useRef 作为 React 开发中不可或缺的工具,它既解决了 DOM 操作的痛点,又为状态管理提供了灵活的补充。

然而,在使用 useRef 时,务必不要滥用。如果某个值需要驱动 UI 更新,优先选择 useState;如果只是需要缓存值或操作 DOM,再使用 useRef

相关推荐
不想上班只想要钱4 分钟前
vue3 ts:声明的一个数组不能将类型“boolean”分配给类型“never”。
前端·vue.js
OEC小胖胖5 分钟前
Next.js 介绍:为什么选择它来构建你的下一个 Web 应用?
开发语言·前端·web·next.js
切糕师学AI16 分钟前
如何建立针对 .NET Core web 程序的线程池的长期监控
java·前端·.netcore
F2E_Zhangmo3 小时前
基于cornerstone3D的dicom影像浏览器 第三章 拖拽seriesItem至displayer上显示第一张dicom
前端·javascript·cornerstone·cornerstone3d·cornerstonejs
gnip8 小时前
Jst执行上下文栈和变量对象
前端·javascript
excel8 小时前
🐣 最简单的卷积与激活函数指南(带示例)
前端
醉方休9 小时前
npm/pnpm软链接的优点和使用场景
前端·npm·node.js
拉不动的猪9 小时前
简单回顾下Weakmap在vue中为何不能去作为循环数据源,以及替代方案
前端·javascript·vue.js
How_doyou_do9 小时前
数据传输优化-异步不阻塞处理增强首屏体验
开发语言·前端·javascript
奇舞精选9 小时前
超越Siri的耳朵:ASR与Whisper零代码部署实战指南
前端·人工智能·aigc