react useref(长文讲解)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观

在 React 开发中,开发者常常需要直接操作 DOM 元素或存储临时数据,但 React 的状态管理机制(如 useState)有时无法满足这些需求。这时,一个名为 useRef 的 Hook 就像一位灵活的“中间人”,在虚拟 DOM 与真实 DOM 之间架起了一座桥梁。本文将深入探讨 useRef 的核心原理、使用场景及进阶技巧,帮助开发者高效利用这一工具解决实际问题。


什么是 useRef?

useRef 是 React 提供的一个 Hook,用于创建可变的引用(reference),其核心特性是:

  1. 不会触发组件重渲染:修改 useRef 的值不会导致组件重新渲染;
  2. 直接访问 DOM 元素:通过 ref 属性绑定到 DOM 元素,获取其真实引用;
  3. 存储临时数据:适合保存不需要触发 UI 更新的临时变量。

基础用法示例

import { useRef } from "react";

function MyComponent() {
  const inputRef = useRef(null);

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

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
}

在上述代码中,useRef(null) 初始化了一个引用对象,其 .current 属性用于存储实际值。通过 ref={inputRef} 将输入框与引用绑定,即可直接调用 inputRef.current.focus() 操作 DOM。


useRef 的核心概念与类比

1. 引用的“仓库”特性

可以将 useRef 比作一个“仓库”:

  • 仓库的入口(.current).current 是仓库的存储空间,开发者可以随时存入或取出数据;
  • 仓库的稳定性:仓库本身不会因数据变化而“搬迁”,即使组件多次渲染,useRef 的引用对象始终指向同一个仓库。

这种特性使其成为存储临时数据的理想选择,例如缓存计算结果或记录组件生命周期状态。

2. 与 useState 的对比

特性useStateuseRef
数据更新方式通过 setState 触发重渲染直接修改 .current 属性
适用场景需要状态变化影响 UI直接操作 DOM 或存储临时值

例如,若需要记录用户点击次数,但无需每次点击都更新 UI,useRef 是更高效的选择:

function ClickCounter() {
  const countRef = useRef(0);

  const handleClick = () => {
    countRef.current += 1;
    console.log("Clicked:", countRef.current);
  };

  return <button onClick={handleClick}>Click me</button>;
}

useRef 的典型应用场景

场景 1:直接操作 DOM 元素

当需要聚焦输入框、滚动到指定位置或手动触发动画时,useRef 是最直接的解决方案。

案例:自动聚焦输入框

function AutoFocusInput() {
  const inputRef = useRef();

  useEffect(() => {
    inputRef.current?.focus();
  }, []);

  return <input ref={inputRef} type="text" />;
}

在此示例中,通过 useEffect 在组件挂载时调用 focus(),确保输入框自动获得焦点。

场景 2:避免不必要的重渲染

某些数据(如第三方库实例或临时计算结果)不需要触发 UI 更新,此时 useRef 可避免 useState 引发的重渲染开销。

案例:缓存复杂计算结果

function MemoizedComponent({ value }) {
  const cacheRef = useRef({});

  if (!cacheRef.current[value]) {
    cacheRef.current[value] = heavyCalculation(value);
  }

  return <div>{cacheRef.current[value]}</div>;
}

此处通过 useRef 缓存计算结果,避免重复执行耗时操作。

场景 3:与第三方库集成

当使用 D3.js、Three.js 等需要直接操作 DOM 的库时,useRef 可轻松获取目标元素:

function Chart() {
  const chartRef = useRef();

  useEffect(() => {
    const chart = new SomeChartLibrary(chartRef.current);
    return () => chart.destroy();
  }, []);

  return <div ref={chartRef} style={{ width: 400, height: 400 }} />;
}

进阶技巧与常见误区

技巧 1:结合 useEffect 管理生命周期

通过 useRefuseEffect 配合,可实现类似类组件 componentDidMountcomponentWillUnmount 的逻辑:

function LifecycleExample() {
  const cleanupRef = useRef();

  useEffect(() => {
    const subscription = someAPI.subscribe();
    cleanupRef.current = () => subscription.unsubscribe();
    return () => cleanupRef.current?.();
  }, []);

  return <div>...</div>;
}

此方法避免了在 useEffect 的依赖数组中传递函数导致的无限循环问题。

技巧 2:创建不可变的“标识符”

useRefReact.useCallback 结合,可生成唯一且稳定的标识符:

const idRef = useRef({ id: 0 });
const nextId = useCallback(() => idRef.current.id++, []);

常见误区

  1. 误用 useRef 替代状态管理:若数据变化需要触发 UI 更新,应优先使用 useStateuseContext
  2. 忽略 .current 的初始值:直接访问 inputRef.current 可能为 null,需添加空值检查或默认值;
  3. 过度依赖 ref 的稳定性:虽然 useRef 对象在多次渲染中保持不变,但其 .current 值仍可被修改,需谨慎管理。

实战案例:动画控制与性能优化

案例:手动控制动画帧

通过 useRef 存储动画帧 ID,实现可暂停/恢复的动画:

function Animation() {
  const canvasRef = useRef();
  const rafIdRef = useRef();

  const draw = () => {
    // 绘制逻辑
    rafIdRef.current = requestAnimationFrame(draw);
  };

  const startAnimation = () => {
    if (!rafIdRef.current) {
      draw();
    }
  };

  const stopAnimation = () => {
    cancelAnimationFrame(rafIdRef.current);
    rafIdRef.current = undefined;
  };

  return (
    <div>
      <canvas ref={canvasRef} />
      <button onClick={startAnimation}>Start</button>
      <button onClick={stopAnimation}>Stop</button>
    </div>
  );
}

案例:优化高频 DOM 操作

在需要频繁操作 DOM 的场景中,useRef 可避免通过 document.querySelector 反复查找元素:

function ToggleElement() {
  const elementRef = useRef();

  const toggleVisibility = () => {
    elementRef.current.style.display =
      elementRef.current.style.display === "none" ? "block" : "none";
  };

  return (
    <div>
      <div ref={elementRef}>Toggle me!</div>
      <button onClick={toggleVisibility}>Toggle</button>
    </div>
  );
}

总结

useRef 是 React 开发中不可或缺的工具,它解决了直接操作 DOM、存储临时数据及优化性能等核心问题。通过理解其“仓库”特性与使用场景,开发者可以:

  1. 高效管理 DOM 元素,避免冗余渲染;
  2. 灵活应对第三方库集成与复杂状态需求;
  3. 避免常见误区,写出更稳定、高效的代码。

掌握 useRef 的本质与技巧,将帮助开发者在 React 项目中实现更优雅的设计与更流畅的用户体验。


(全文约 1800 字)

最新发布