React `useRef` Hook: 全面解析

React useRef Hook: 全面解析

useRef 是 React 中用于创建持久化可变引用的核心 Hook,它解决了函数组件中无法直接访问 DOM 元素和保存可变值的问题。

核心特性

  1. 跨渲染持久化:返回的对象在组件整个生命周期中保持不变
  2. 变更不会触发重渲染 :修改 .current 属性不会导致组件更新
  3. 直接访问 DOM 节点:最常用的场景
  4. 保存任意可变值:类似类组件的实例属性

基础语法

javascript 复制代码
const refContainer = useRef(initialValue);
  • initialValue:初始值(通常为 null
  • refContainer.current:访问/修改引用值

主要使用场景

1. 访问 DOM 元素(最常见用法)

jsx 复制代码
function TextInput() {
  const inputRef = useRef(null);

  const focusInput = () => {
    inputRef.current.focus(); // 直接操作DOM
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>聚焦输入框</button>
    </div>
  );
}

2. 存储可变值(不触发重渲染)

jsx 复制代码
function Timer() {
  const [count, setCount] = useState(0);
  const intervalRef = useRef(); // 存储定时器ID

  useEffect(() => {
    intervalRef.current = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);
    
    return () => clearInterval(intervalRef.current);
  }, []);

  return <div>计时: {count} 秒</div>;
}

3. 保存上一次状态

jsx 复制代码
function Counter() {
  const [count, setCount] = useState(0);
  const prevCountRef = useRef();
  
  useEffect(() => {
    prevCountRef.current = count; // 在渲染后更新
  });
  
  return (
    <div>
      <p>当前值: {count}</p>
      <p>上一次值: {prevCountRef.current ?? '无'}</p>
      <button onClick={() => setCount(c => c + 1)}>增加</button>
    </div>
  );
}

4. 存储复杂计算结果(跳过重复计算)

jsx 复制代码
function ExpensiveComponent() {
  const [value, setValue] = useState(0);
  const computedValueRef = useRef();
  
  if (!computedValueRef.current) {
    // 只计算一次
    computedValueRef.current = heavyComputation(value);
  }
  
  return <div>{computedValueRef.current}</div>;
}

useState 的关键区别

特性 useRef useState
触发重渲染
存储类型 可变值 (current 属性) 不可变状态
访问方式 直接访问 .current 通过状态变量
更新机制 同步更新 异步批量更新
适合场景 DOM引用/缓存值/避免重渲染计算 UI渲染相关状态

高级用法

1. 自定义 Hook 封装

javascript 复制代码
// 获取上一次值的Hook
function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  }, [value]);
  return ref.current;
}

2. 结合 forwardRef 暴露组件内部 DOM

jsx 复制代码
const CustomInput = forwardRef((props, ref) => {
  return <input ref={ref} {...props} />;
});

function Parent() {
  const inputRef = useRef();
  // 可直接操作 CustomInput 内部的 input
  return <CustomInput ref={inputRef} />;
}

3. 测量 DOM 元素

jsx 复制代码
function MeasureExample() {
  const divRef = useRef(null);
  const [size, setSize] = useState({});
  
  useLayoutEffect(() => {
    if (divRef.current) {
      setSize({
        width: divRef.current.offsetWidth,
        height: divRef.current.offsetHeight
      });
    }
  }, []);

  return (
    <div ref={divRef}>
      元素尺寸: {size.width} x {size.height}
    </div>
  );
}

注意事项

  1. 不要在渲染期间修改 refs

    修改 ref.current 应放在事件处理、useEffect 或生命周期方法中

  2. 避免过度使用

    优先考虑 React 数据流(props/state),仅当确实需要时才使用 refs

  3. 初始渲染时 current 为 null

    访问 DOM 元素时需做空值检查:

    js 复制代码
    useEffect(() => {
      if (inputRef.current) {
        inputRef.current.focus();
      }
    }, []);
  4. 服务端渲染(SSR)问题

    SSR 期间 refs 不会附加到 DOM,应在 useEffect 中使用


性能优化

jsx 复制代码
function HeavyComponent() {
  const elementRef = useRef();
  
  // 避免在渲染期间进行昂贵操作
  useEffect(() => {
    if (elementRef.current) {
      performExpensiveDOMOperation(elementRef.current);
    }
  }, []);

  return <div ref={elementRef}>...</div>;
}

总结

useRef 是 React 函数组件的"逃生舱",主要解决两类问题:

  1. 访问 DOM 元素(表单聚焦、媒体控制、动画等)
  2. 存储可变值(计时器ID、缓存数据、前值记录等)

正确使用原则:

  • 需要直接操作 DOM 时使用
  • 需要存储与渲染无关的值时使用
  • 避免在渲染期间修改 .current
  • 结合 forwardRef 暴露组件内部 DOM
  • 优先使用 React 数据流,ref 作为最后手段
相关推荐
郑州光合科技余经理2 小时前
本地生活系统:多业务订单字段怎么分账本导出
java·开发语言·前端·数据库·uni-app·php·ai编程
怕浪猫8 小时前
FDE 如何正确解决现场项目工程性的问题
前端·后端·ai编程
西瓜太郎12349 小时前
外层模型卡片与分组详情成功率不一致,应该怎么排查?
前端·typescript·go·软件工程·react
GreenTea9 小时前
vLLM 与 SGLang KV Cache 底层实现机制深度调研报告
前端·后端·算法
几何心凉12 小时前
没有万能模型:我用 AiiOnly Token Plan,把多款大模型的长处拼进同一个项目
前端
kyriewen12 小时前
Claude Code 的额度今天缩水了17%——官方公告上写的是"永久提高25%"
前端·ai编程·claude
雪芽蓝域zzs13 小时前
vue解构平铺VS对象包裹
前端·javascript·vue.js
风骏时光牛马13 小时前
从零到实战,完整AI学习路线
前端
三十而立洋14 小时前
深入理解 Monorepo:子包安装依赖
前端·前端工程化
IT_陈寒14 小时前
Java Stream处理大集合,我的内存怎么就炸了
前端·人工智能·后端