useRef 到底用来干什么?不止获取 DOM 元素

useRef 到底用来干什么?不止获取 DOM 元素

如果你刚接触 React Hooks,useRef 给你的第一印象大概率是: "用来拿 DOM 的"

比如:

ini 复制代码
const inputRef = useRef<HTMLInputElement>(null);

<input ref={inputRef} />

这没错,但这只是 useRef 的"表面工作"。

真正理解 useRef,要抓住一句话:

useRef 是 React 中"跨渲染周期的可变容器"。

这篇文章,我们就从"拿 DOM"开始,逐步拆开 useRef 的本质、常见误区和高级用法。


一、useRef 到底是什么?

1. 定义

r 复制代码
useRef<T>(initialValue: T): { current: T }
  • 返回一个可变对象 { current: T }
  • 在组件的整个生命周期内保持不变
  • 修改 ref.current 不会触发组件重新渲染

这是它和 useState 最大的区别。


二、最基础用法:获取 DOM 元素

1. 获取 DOM

javascript 复制代码
function TextInput() {
  const inputRef = useRef<HTMLInputElement>(null);

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

  return (
    <>
      <input ref={inputRef} />
      <button onClick={focusInput}>聚焦</button>
    </>
  );
}

2. 常见 DOM 场景

  • 聚焦 / 选中文本
  • 测量 DOM 尺寸
  • 滚动控制
  • 集成第三方 DOM 库(如 ECharts、Monaco Editor)
ini 复制代码
const chartRef = useRef<HTMLDivElement>(null);

useEffect(() => {
  if (chartRef.current) {
    const width = chartRef.current.clientWidth;
    // 初始化图表
  }
}, []);

DOM 操作是 useRef 的"第一职业",但不是唯一职业。


三、useRef vs useState:关键区别

这是面试和实战中最容易混淆的点。

特性 useRef useState
是否触发渲染 ❌ 不触发 ✅ 触发
值是否跨渲染 ✅ 是 ✅ 是
是否"React 可控" ❌ 不可控 ✅ 可控
典型用途 DOM、实例、缓存 UI 状态

一个经典例子

javascript 复制代码
function Counter() {
  const countRef = useRef(0);
  const [count, setCount] = useState(0);

  const incrementRef = () => {
    countRef.current++;
    console.log('ref:', countRef.current);
    // UI 不会更新
  };

  const incrementState = () => {
    setCount(c => c + 1);
    // UI 会更新
  };

  return (
    <>
      <p>state: {count}</p>
      <button onClick={incrementRef}>Ref +1</button>
      <button onClick={incrementState}>State +1</button>
    </>
  );
}

👉 结论

  • 需要 UI 响应 → useState
  • 需要"悄悄存点东西" → useRef

四、useRef 的隐藏技能:跨渲染保存可变值

1. 保存上一次的 props / state

ini 复制代码
function usePrevious<T>(value: T): T | undefined {
  const ref = useRef<T>();

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref.current;
}

使用:

ini 复制代码
const prevCount = usePrevious(count);

这在做"前后对比"时非常有用,比如动画、日志、表单脏检查。


2. 保存定时器 / 订阅实例

javascript 复制代码
function Timer() {
  const timerRef = useRef<NodeJS.Timeout | null>(null);

  useEffect(() => {
    timerRef.current = setInterval(() => {
      console.log('tick');
    }, 1000);

    return () => {
      if (timerRef.current) {
        clearInterval(timerRef.current);
      }
    };
  }, []);

  return <div>Timer</div>;
}

✅ 用 useRef 保存"清理对象",比 let timer 更安全。


3. 防止重复初始化(单例)

csharp 复制代码
function useOnce(init: () => any) {
  const ref = useRef<{ value: any }>();

  if (!ref.current) {
    ref.current = { value: init() };
  }

  return ref.current.value;
}

适合:

  • 创建 SDK 实例
  • 初始化 Web Worker
  • 创建全局缓存对象

五、useRef 在闭包陷阱中的救场

React Hooks 的一个经典坑:闭包捕获旧值

问题示例

scss 复制代码
function Chat() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      console.log(count); // 永远是 0
    }, 1000);

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

  return <button onClick={() => setCount(c => c + 1)}>+</button>;
}

用 useRef 解决

scss 复制代码
function Chat() {
  const [count, setCount] = useState(0);
  const countRef = useRef(count);

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

  useEffect(() => {
    const id = setInterval(() => {
      console.log(countRef.current); // ✅ 永远是最新值
    }, 1000);

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

  return <button onClick={() => setCount(c => c + 1)}>+</button>;
}

useRef 是"逃离闭包陷阱"的常用手段。


六、useRef + 自定义 Hook = 强力组合

1. useLatest:永远拿到最新值

csharp 复制代码
function useLatest<T>(value: T) {
  const ref = useRef(value);
  ref.current = value;
  return ref;
}

2. useMountedRef:判断组件是否挂载

ini 复制代码
function useMountedRef() {
  const ref = useRef(false);

  useEffect(() => {
    ref.current = true;
    return () => {
      ref.current = false;
    };
  }, []);

  return ref;
}

常用于异步请求中防止内存泄漏:

scss 复制代码
if (mountedRef.current) {
  setData(res);
}

七、useRef 的常见误区

❌ 误区 1:用 useRef 代替 useState

ini 复制代码
// 错误:UI 不会更新
const countRef = useRef(0);

只要 UI 依赖这个值,就不能用 useRef。


❌ 误区 2:在渲染阶段修改 ref.current

ini 复制代码
// ❌ 不推荐
ref.current = 123;
return <div />;

✅ 修改 ref 应放在:

  • useEffect
  • 事件处理函数
  • 自定义 Hook 内部逻辑

❌ 误区 3:认为 ref 是响应式的

ref.current 变化 不会触发组件更新,这是设计如此,不是 bug。


八、一句话总结 useRef

useRef 是一个"在组件生命周期内存在、可变、不触发渲染的盒子"。

它适合:

  • ✅ 获取 DOM
  • ✅ 保存实例
  • ✅ 缓存跨渲染的值
  • ✅ 解决闭包问题
  • ✅ 控制副作用生命周期

不适合:

  • ❌ UI 状态
  • ❌ 需要响应式的数据

九、什么时候该用 useRef?

你可以问自己三个问题:

  1. 这个值是否需要跨渲染存在?
  2. 修改它是否需要触发 UI 更新?
  3. 我是否只是想"存点东西",而不是"驱动 UI"?

如果答案是 "是 / 否 / 是" ​ → 用 useRef

相关推荐
大勇前进1 小时前
React Hooks 常见 5 大坑:写完代码疯狂 Bug,避坑总结
后端
颜进强1 小时前
04 - 一张图看懂 OpenSpec 变更的一生:从创建到归档
前端·后端·ai编程
Cobyte1 小时前
Claude Code 的 Task System 的实现原理
后端·aigc·ai编程
小强19881 小时前
useEffect 完整使用指南:依赖数组、闭包陷阱、清理函数实战
后端
智驭未来掌门人1 小时前
在windows下快速搭建go2rtc流媒体平台
后端
大白801 小时前
为什么不要滥用 useMemo 和 useCallback?过度优化反而更卡
后端
神奇小汤圆2 小时前
Codex 子 Agent 配置指南:让 Sol 当军师,Luna 当搬砖工
后端
ClouGence2 小时前
2026 年 4 款数据库管理工具推荐:免费、开源、付费怎么选?
数据库·后端·开源
大勇前进3 小时前
React 中 State 为什么不能直接修改?很多新手一直没懂
后端