5.useRef 有哪些常见的应用场景? 副本

查看飞书原文 ↗3,999 字符
useRef 是 React Hooks 中一个独特且功能强大的工具。与 useState 不同,**更新 useRef 的值不会触发组件的重新渲染**。这个核心特性决定了它的主要用途:在需要一个“可变容器”来跨渲染周期持久化数据,但又不希望这个数据的变化影响视图时,useRef 就成了最佳选择。​
​
本质上,我们可以把 useRef 看作是函数组件实例上的一个“储物箱”,我们可以随时存取物品,而不用每次都重新整理(渲染)整个房间。​
​
下面我们来探讨几个最常见的应用场景。​
​
1. 访问和操作 DOM 元素​
​
这是 useRef 最广为人知的用途。在 React 的声明式世界里,我们通常不直接操作 DOM。但总有一些场景需要我们“破例”,例如:​
​
•
自动聚焦输入框​
•
获取元素的尺寸或位置​
•
控制视频或音频的播放​
•
集成需要直接访问 DOM 的第三方库(如 D3.js)​
​
在这些情况下,useRef 提供了一个可靠的桥梁。​
​
工作流程:​
​
1.
创建一个 ref 对象:const inputRef = useRef(null);​
2.
将其附加到 JSX 元素的 ref 属性上:<input ref={inputRef} />​
3.
在 useEffect 或事件处理函数中,通过 inputRef.current 访问该 DOM 节点。​
​
示例:页面加载后自动聚焦输入框​
​
import React, { useRef, useEffect } from 'react';

function AutoFocusInput() {
  // 1. 创建一个 ref
  const inputRef = useRef(null);

  useEffect(() => {
    // 3. 在 effect 中访问 DOM 节点
    // inputRef.current 指向的就是真实的 <input> DOM 元素
    if (inputRef.current) {
      inputRef.current.focus();
    }
  }, []); // 空依赖数组确保 effect 仅在组件挂载时运行一次

  return (
    <div>
      <p>输入框将在加载后自动获得焦点:</p>
      {/* 2. 将 ref 附加到 DOM 元素 */}
      <input ref={inputRef} type="text" placeholder="请在这里输入..." />
    </div>
  );
}
​
2. 存储跨渲染周期的可变值​
​
这是 useRef 更为精妙的用法。当我们有一个值,需要在多次渲染之间保持不变,但它的变化又不应该引起视图更新时,useRef 就非常适用。最典型的例子是管理定时器。​
​
场景:管理 setTimeout 或 setInterval​
​
如果我们将定时器 ID 存储在 useState 中,每次 setTimerId 都会导致不必要的重渲染。而使用 useRef,我们可以悄无声息地在后台存储和清除定时器。​
​
示例:一个简单的秒表​
​
import React, { useState, useRef } from 'react';

function Stopwatch() {
  const [seconds, setSeconds] = useState(0);
  
  // 使用 ref 来存储 interval ID
  const intervalRef = useRef(null);

  const handleStart = () => {
    // 防止重复启动
    if (intervalRef.current) return;

    intervalRef.current = setInterval(() => {
      // 注意:这里使用函数式更新,避免依赖外部的 `seconds` 变量
      setSeconds(prevSeconds => prevSeconds + 1);
    }, 1000);
  };

  const handleStop = () => {
    if (intervalRef.current) {
      clearInterval(intervalRef.current);
      intervalRef.current = null; // 清理 ref
    }
  };

  const handleReset = () => {
    handleStop();
    setSeconds(0);
  };

  return (
    <div>
      <h2>秒表: {seconds}s</h2>
      <button onClick={handleStart}>开始</button>
      <button onClick={handleStop}>停止</button>
      <button onClick={handleReset}>重置</button>
    </div>
  );
}
​
在这个例子中,intervalRef.current 的变化(从 null 变为一个数字 ID,再变回 null)完全独立于组件的渲染流程,实现了高效的状态管理。​
​
3. 缓存上一次的 Props 或 State​
​
有时我们需要在渲染时拿到上一次的某个值,以便进行比较或计算。例如,我们想知道一个数字是增大了还是减小了。useRef 和 useEffect 的组合可以优雅地实现这个需求。​
​
工作流程:​
​
1.
用 useRef 创建一个 ref 来存储上一次的值。​
2.
在 useEffect 中,**先执行逻辑**(此时 ref 中还是上一次的值),**然后更新 ref** 为当前值,为下一次渲染做准备。​
​
示例:显示当前值和上一个值​
​
import React, { useState, useEffect, useRef } from 'react';

function PreviousValueDisplay({ value }) {
  // 用 ref 存储上一次的 value
  const prevValueRef = useRef();

  useEffect(() => {
    // effect 会在每次渲染完成后运行
    // 所以在这次更新 ref 之前,它存储的还是上一次渲染的值
    prevValueRef.current = value;
  }, [value]); // 依赖 value,确保 value 变化时更新 ref

  // 在渲染时,prevValueRef.current 还是上一次的值
  const prevValue = prevValueRef.current;

  return (
    <p>当前值: {value}, 上一个值: {prevValue === undefined ? 'N/A' : prevValue}</p>
  );
}

function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>增加</button>
      <button onClick={() => setCount(c => c - 1)}>减少</button>
      <PreviousValueDisplay value={count} />
    </div>
  );
}
这个模式非常实用,可以看作是实现了一个自定义 Hook usePrevious 的基础。​
​
useState vs useRef:如何选择?​
​
为了更好地理解 useRef,我们可以建立一个清晰的选择标准:​
​
​
​
特性​
​
​
useState​
​
​
useRef​
​
​
触发重渲染​
​
​
是,setState 会触发组件重新渲染。​
​
​
否,修改 .current 属性不会触发重渲染。​
​
​
主要用途​
​
​
管理组件的声明式状态,即那些需要反映在 UI 上的数据。​
​
​
存储不需要触发 UI 更新的数据,或用于命令式地访问 DOM。​
​
​
值更新方式​
​
​
异步更新,通过 setState 函数。​
​
​
同步更新,直接修改 .current 属性。​
​
​
何时使用​
​
​
当数据变化需要让用户看到时。​
​
​
当你需要一个“后台”变量,或者需要与 React 之外的世界(如 DOM)交互时。​
​
​
​
总结​
​
useRef 为我们提供了一个逃生舱,让我们可以在必要时跳出 React 纯粹的声明式数据流。它就像是函数组件的“实例属性”,为我们处理那些与渲染无关的副作用、状态和外部交互提供了强大的支持。​
​
掌握 useRef 的这几个核心场景,可以帮助我们编写出更干净、更高效、也更灵活的 React 组件。​
​
本文暂未被其它文档引用