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 作为最后手段
相关推荐
寅时码10 分钟前
无需安装,纯浏览器实现跨平台文件、文本传输,支持断点续传、二维码、房间码加入、粘贴传输、拖拽传输、多文件传输
前端·后端·架构
哔哩哔哩技术33 分钟前
哔哩哔哩Android视频编辑页的架构升级
前端
小小小小宇40 分钟前
重提Vue 3 性能提升
前端
eason_fan41 分钟前
React 源码执行流程
前端·源码阅读
will_we1 小时前
服务器主动推送之SSE (Server-Sent Events)探讨
前端·后端
yume_sibai1 小时前
Less Less基础
前端·css·less
小小小小宇1 小时前
重提Vue3 的 Diff 算法
前端
清岚_lxn1 小时前
前端js通过a标签直接预览pdf文件,弹出下载页面问题
前端·javascript·pdf
不爱说话郭德纲1 小时前
别再花冤枉钱!手把手教你免费生成iOS证书(.p12) + 打包IPA(超详细)
前端·ios·app
代码的余温1 小时前
Vue多请求并行处理实战指南
前端·javascript·vue.js