React useRef 常用场景总结

useRef 是 React 提供的一个 Hook,用于在函数组件中持有一个 可变的引用对象
它的特点是:

  • .current 属性可以存储任意可变值。
  • 修改 .current 不会触发组件重新渲染。
  • 常用于 DOM 引用保存变量缓存值 等场景。

1. 获取和操作 DOM 节点

在 React 中,推荐通过 ref 获取 DOM 元素,而不是 document.querySelector

import { useRef, useEffect } from "react";

export default function InputFocus() {
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    // 组件挂载后自动聚焦
    inputRef.current?.focus();
  }, []);

  return <input ref={inputRef} placeholder="自动聚焦输入框" />;
}

✅ 典型场景:表单聚焦、滚动定位、播放视频等。


2. 保存跨渲染周期的变量

useRef 可以保存一些数据,而不需要因为依赖 useState 而导致组件重复渲染。

import { useRef } from "react";

export default function Timer() {
  const countRef = useRef(0);

  const handleClick = () => {
    countRef.current += 1;
    console.log("点击次数:", countRef.current);
  };

  return <button onClick={handleClick}>点我</button>;
}

✅ 适合存储:定时器 ID、上一次的值、函数调用次数等。


3. 保存定时器 / 事件订阅等引用

import { useRef, useEffect } from "react";

export default function Countdown() {
  const timerRef = useRef<number>();

  useEffect(() => {
    timerRef.current = window.setInterval(() => {
      console.log("倒计时中...");
    }, 1000);

    return () => {
      // 清理定时器
      clearInterval(timerRef.current);
    };
  }, []);

  return <div>倒计时示例</div>;
}

✅ 场景:保存 setInterval / setTimeout,避免重复绑定。


4. 缓存前一次渲染的值

通过 useRef 可以追踪 上一次的 props 或 state

import { useState, useEffect, useRef } from "react";

export default function PrevValueDemo() {
  const [count, setCount] = useState(0);
  const prevCount = useRef<number>();

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

  return (
    <div>
      <p>当前值: {count}</p>
      <p>上一次的值: {prevCount.current}</p>
      <button onClick={() => setCount(count + 1)}>增加</button>
    </div>
  );
}

✅ 场景:对比前后 state/props,做差异化处理。


5. 避免闭包陷阱(保存最新的函数或对象)

在 React 中,闭包陷阱经常出现在 定时器、事件回调、订阅 等场景:

  • 如果直接在回调里使用 state,可能拿到的是“旧值”。
  • 可以用 useRef 保存最新的函数或对象,避免闭包导致的“过期数据”。

示例一:保存最新函数

import { useState, useEffect, useRef } from "react";

export default function ClosureTrap() {
  const [count, setCount] = useState(0);
  const callbackRef = useRef<() => void>();

  useEffect(() => {
    callbackRef.current = () => {
      console.log("最新的 count:", count);
    };
  }, [count]);

  useEffect(() => {
    const id = setInterval(() => {
      callbackRef.current?.();
    }, 1000);

    return () => clearInterval(id);
  }, []);

  return (
    <button onClick={() => setCount(count + 1)}>
      增加: {count}
    </button>
  );
}

示例二:保存实例化对象(避免重复创建)

有些场景需要创建一个对象(例如 WebSocket、音频处理器、类实例等),如果放在组件函数体里会在每次渲染时重新实例化。
useRef 可以保证实例只被创建一次,并且始终保持引用最新:

import { useRef, useEffect } from "react";

class MyService {
  connect() {
    console.log("服务已连接");
  }
}

export default function ServiceDemo() {
  const serviceRef = useRef<MyService>();

  if (!serviceRef.current) {
    serviceRef.current = new MyService();
  }

  useEffect(() => {
    serviceRef.current?.connect();
  }, []);

  return <div>服务初始化完成</div>;
}

✅ 典型应用:

  • 保存 WebSocket 连接对象
  • 保存 AudioContextMediaRecorder 等实例
  • 保存第三方库(如 Chart.js、Three.js)的实例化对象

6. 转发 Ref(forwardRef)

在自定义组件中转发 ref,以便父组件可以直接获取子组件的 DOM。

import { forwardRef, useRef, useImperativeHandle } from "react";

const MyInput = forwardRef((props, ref) => {
  const inputRef = useRef<HTMLInputElement>(null);

  useImperativeHandle(ref, () => ({
    focus: () => inputRef.current?.focus()
  }));

  return <input ref={inputRef} {...props} />;
});

export default function Parent() {
  const ref = useRef<{ focus: () => void }>(null);

  return (
    <div>
      <MyInput ref={ref} />
      <button onClick={() => ref.current?.focus()}>聚焦输入框</button>
    </div>
  );
}

✅ 场景:组件封装时,提供对内部 DOM 的操作能力。


总结

useRef 的常用场景可以归纳为:

  1. 获取 DOM 节点(替代 document.querySelector
  2. 保存变量(不会触发渲染)
  3. 保存定时器 / 事件订阅引用
  4. 保存上一次的值
  5. 避免闭包陷阱(保持函数或对象引用最新)
  6. 转发 Ref 给子组件

简单一句话:useRef = 可变的盒子,存储数据但不触发渲染。

更多推荐