useRef:React 中那个“默默无闻却不可或缺”的幕后英雄

37 阅读4分钟

别再只盯着 useState 了!useRef 才是你组件里最稳的“老黄牛”


在 React 的 Hooks 家族中,useState 是当之无愧的流量明星——谁不为它的响应式能力疯狂打 call?但今天,我们要把聚光灯转向一个低调到几乎被忽略的角色:useRef

它不像 useState 那样一变就让整个组件重渲染;也不像 useEffect 那样自带“生命周期光环”。它就像公司里那个从不抢功劳、却总在关键时刻修好服务器的运维大哥——默默奉献,稳如泰山

今天,我们就来揭开 useRef 的神秘面纱,看看这个“可持久化引用对象”到底有多香!


🎭 useRef 是什么?和 useState 有啥区别?

先上结论:

  • 相同点:两者都是用来“存东西”的容器。

  • 不同点

    • useState 存的是响应式状态——一改就触发重渲染。
    • useRef 存的是可变但非响应式的引用——改了天王老子也不会重渲染组件。
const [count, setCount] = useState(0);     // 改了 → 组件重新渲染
const ref = useRef(0);                     // ref.current = 1; → 组件纹丝不动

你可以把 useRef 想象成一个带锁的小抽屉:你随时能往里塞东西、取东西,但 React 根本不在乎你动没动它——除非你自己主动去用它。


🛠️ 场景一:获取 DOM 元素?交给 useRef!

在 Vue 里,我们用 ref="xxx" + this.$refs.xxx 来操作 DOM。
在 React 函数组件里?useRef 就是你的 $refs

import { useRef, useEffect } from 'react';

export default function App() {
  const inputRef = useRef(null);

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

  return <input ref={inputRef} placeholder="我自动聚焦啦~" />;
}

💡 注意:ref={inputRef} 这个写法是 React 的“魔法语法”——当你把 useRef 对象赋给元素的 ref 属性时,React 会自动把 DOM 节点塞进 ref.current 里!

这比 document.getElementById 高级多了——无需 ID,无需全局污染,精准绑定,安全可靠


⏱️ 场景二:存储“可变但不想触发重渲染”的值

想象一个经典需求:实现一个可启停的定时器

如果你用 useStateintervalId,每次 setInterval 都要更新状态,结果就是——组件疯狂重渲染,性能直接拉胯。

这时候,useRef 就派上用场了!

import { useState, useRef } from 'react';

export default function TimerDemo() {
  const [count, setCount] = useState(0);
  const intervalId = useRef(null); // 关键:用 useRef 存 ID!

  const start = () => {
    if (intervalId.current) return; // 防止重复启动
    intervalId.current = setInterval(() => {
      console.log('tick~~~~');
    }, 1000);
  };

  const stop = () => {
    clearInterval(intervalId.current);
    intervalId.current = null;
  };

  return (
    <>
      <button onClick={start}>开始</button>
      <button onClick={stop}>停止</button>
      <p>Count: {count}</p>
      <button onClick={() => setCount(c => c + 1)}>+1</button>
    </>
  );
}

优势

  • intervalId.current 可以随意修改,不会触发重渲染
  • 组件卸载时,记得在 useEffect 里清理定时器(避免内存泄漏)!

🔁 useRef vs useState:什么时候该用谁?

场景推荐 Hook原因
需要 UI 随数据变化而更新useState响应式,自动触发重渲染
需要保存一个值,但不希望引起重渲染useRef非响应式,性能更优
需要操作 DOM 元素useRefReact 官方推荐方式
需要跨渲染周期保持某个值(如前一次的 props)useRef可持久化,值不会“丢失”

🧠 小技巧
如果你发现某个 useState 的值只在副作用里用,从来不用于 JSX 渲染,那它很可能应该换成 useRef


🤔 为什么 useRef 不是响应式的?这是缺陷吗?

不是缺陷,是设计哲学!

React 的核心思想是:UI = f(state) 。只有状态变了,UI 才该变。

useRef 存的不是“状态”,而是“辅助数据”——比如 DOM 引用、定时器 ID、WebSocket 实例、前一次的计算结果等。这些数据的变化不应该影响 UI 结构

所以,useRef 的“非响应式”恰恰是它的优点:让你在不打扰 React 渲染机制的前提下,安全地管理副作用相关的可变数据。


🎉 总结:useRef —— React 的“隐形守护者”

  • ✅ 获取 DOM 元素?useRef 是官方指定方案。
  • ✅ 存储非响应式可变值?useRef 性能更优。
  • ✅ 跨渲染周期保持数据?useRef 稳如老狗。
  • ❌ 别用它来存需要驱动 UI 更新的数据——那是 useState 的地盘!

下次当你写 useEffect 里需要保存某个“中间变量”时,不妨问问自己: “我真的需要让它触发重渲染吗?”
如果答案是否定的——恭喜你,useRef 正在对你微笑 😊


彩蛋
有人说 useRef 是 React Hooks 里最被低估的 Hook。
我说:它不是被低估,而是太谦虚——真正的高手,从不喧哗