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

相关推荐
想学后端的前端工程师20 分钟前
【Vue3组合式API实战指南:告别Options API的烦恼】
前端·javascript·vue.js
一勺-_-29 分钟前
mermaid图片如何保存成svg格式
开发语言·javascript·ecmascript
否子戈30 分钟前
WebCut前端视频编辑UI框架一周开源进度
前端·音视频开发·ui kit
昔人'1 小时前
`corepack` 安装pnpm
前端·pnpm·node·corepack
萌萌哒草头将军1 小时前
pnpm + monorepo 才是 AI 协同开发的最佳方案!🚀🚀🚀
前端·react.js·ai编程
hboot2 小时前
💪别再迷茫!一份让你彻底掌控 TypeScript 类型系统的终极指南
前端·typescript
GISer_Jing2 小时前
深入拆解Taro框架多端适配原理
前端·javascript·taro
毕设源码-邱学长2 小时前
【开题答辩全过程】以 基于VUE的藏品管理系统的设计与实现为例,包含答辩的问题和答案
前端·javascript·vue.js
San30.3 小时前
深入理解 JavaScript:手写 `instanceof` 及其背后的原型链原理
开发语言·javascript·ecmascript
北冥有一鲲3 小时前
LangChain.js:RAG 深度解析与全栈实践
开发语言·javascript·langchain