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 作为最后手段
相关推荐
高斯林.神犇34 分钟前
javaWeb基础
前端·chrome
用户214118326360238 分钟前
dify案例分享-Qwen3-VL+Dify:从作业 OCR 到视频字幕,多模态识别工作流一步教,附体验链接
前端
南屿im1 小时前
把代码变成“可改的树”:一文读懂前端 AST 的原理与实战
前端·javascript
charlie1145141911 小时前
从《Life of A Pixel》来看Chrome的渲染机制
前端·chrome·学习·渲染·浏览器·原理分析
HWL56791 小时前
输入框内容粘贴时 &nbsp; 字符净化问题
前端·vue.js·后端·node.js
梦6501 小时前
JQ 的 AJAX 请求方法
前端·ajax
ObjectX前端实验室1 小时前
【react18原理探究实践】分层解析React Fiber 核心工作流程
前端·react.js
IT_陈寒2 小时前
「JavaScript 性能优化:10个让V8引擎疯狂提速的编码技巧」
前端·人工智能·后端
ObjectX前端实验室3 小时前
【react18原理探究实践】scheduler原理之Task 完整生命周期解析
前端·react.js
ObjectX前端实验室3 小时前
【react18原理探究实践】调度器(Scheduler)原理深度解析
前端·react.js