React Refs(保姆级教程)

更新时间:

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

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

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

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

什么是 React Refs?

在 React 开发中,我们常常会遇到这样的场景:需要直接操作某个 DOM 元素,或者在组件间传递复杂的数据结构。这时候,一个名为 React Refs 的功能就显得尤为重要。它可以看作是 React 组件与底层 DOM 之间的“钥匙”,帮助开发者突破虚拟 DOM 的限制,实现对特定元素的直接控制。

Refs 的核心作用

  • 直接访问 DOM 元素:当需要聚焦输入框、测量元素尺寸或操作第三方库时,Refs 提供了安全的访问通道。
  • 管理复杂状态:在无法通过 Props 或 State 传递时,Refs 可以存储并更新组件的内部状态。
  • 协调组件行为:例如在表单验证时,通过 Refs 获取输入值进行校验。

Refs 的比喻理解

想象你有一座由乐高积木搭建的城堡(React 组件树),每个积木块(组件)都被精心排列。但当你需要移动某块特定的积木时,却无法通过常规的拼接方式直接触碰它。这时 Refs 就像是一把“万能钥匙”,允许你绕过常规的建造规则,直接定位并操作目标积木。


Refs 的基本用法

类组件中的 Refs

在基于 class 的组件中,可以通过 createRef 方法创建 Ref。以下是一个简单的例子:

class FocusInput extends React.Component {
  constructor() {
    super();
    this.inputRef = React.createRef();
  }

  componentDidMount() {
    // 自动聚焦输入框
    this.inputRef.current.focus();
  }

  render() {
    return <input ref={this.inputRef} />;
  }
}

关键点解析

  • React.createRef() 创建了一个可复用的 Ref 对象。
  • this.inputRef.current 是 Ref 的真实目标,即 DOM 元素或组件实例。
  • componentDidMount 生命周期中,确保 DOM 已渲染完成再调用方法。

函数组件中的 Refs

随着 Hooks 的引入,函数组件可以通过 useRef 钩子实现相同功能:

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

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

  return <input ref={inputRef} />;
}

对比分析: | 特性 | 类组件 Refs | 函数组件 useRef | |---------------------|--------------------------|-------------------------| | 创建方式 | React.createRef() | useRef(initialValue) | | 数据访问 | this.ref.current | ref.current | | 依赖管理 | 需要维护实例属性 | 无需额外状态管理 | | 适用场景 | 旧版代码维护 | 新项目首选方案 |


Refs 的常见使用场景

场景一:表单自动聚焦

在用户注册页面,密码输入框需要在页面加载后自动获得焦点。通过 Refs 可以轻松实现:

function LoginForm() {
  const passwordRef = useRef();

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

  return (
    <div>
      <input type="text" placeholder="用户名" />
      <input
        type="password"
        ref={passwordRef}
        placeholder="密码"
      />
    </div>
  );
}

效果对比: | 传统方法 | Refs 实现 | |-----------------------|-------------------------| | 需要手动查询 DOM 节点 | 通过 Ref 直接引用元素 | | 可能引发性能问题 | 受 React 生命周期保护 | | 难以维护 | 代码结构清晰易读 |

场景二:手动控制媒体播放

当使用 HTML5 视频元素时,需要通过 Refs 调用播放方法:

function VideoPlayer() {
  const videoRef = useRef();

  const handlePlay = () => {
    videoRef.current.play();
  };

  return (
    <div>
      <video ref={videoRef} src="example.mp4" />
      <button onClick={handlePlay}>播放</button>
    </div>
  );
}

场景三:第三方库集成

在使用 D3.js 或 Chart.js 等库时,需要通过 Refs 获取画布元素:

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

  useEffect(() => {
    const chart = new Chart(chartRef.current, {
      type: 'line',
      data: {/* 数据配置 */},
    });
    return () => chart.destroy();
  }, []);

  return <canvas ref={chartRef} />;
}

Refs 的高级技巧

1. Refs 的合并使用

当需要将 Ref 传递给多个目标时,可以使用 useImperativeHandleforwardRef

// 子组件
const ChildComponent = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
    customMethod() {
      // 自定义暴露的方法
    },
  }));
  return <div>子组件内容</div>;
});

// 父组件
function ParentComponent() {
  const childRef = useRef();
  return (
    <div>
      <ChildComponent ref={childRef} />
      <button onClick={() => childRef.current.customMethod()}>
        触发子组件方法
      </button>
    </div>
  );
}

2. Refs 的动态管理

在列表渲染时,可以通过数组形式管理多个 Ref:

function DynamicRefsList() {
  const itemRefs = useRef([]);

  const focusItem = (index) => {
    itemRefs.current[index].focus();
  };

  return (
    <div>
      {[0, 1, 2].map((_, index) => (
        <input
          key={index}
          ref={(el) => (itemRefs.current[index] = el)}
        />
      ))}
      <button onClick={() => focusItem(1)}>聚焦第二个输入框</button>
    </div>
  );
}

3. Refs 的清理与生命周期

在组件卸载时,需要确保 Ref 的正确清理:

useEffect(() => {
  const timer = setInterval(() => {
    // 使用 ref.current 的逻辑
  }, 1000);

  return () => {
    clearInterval(timer);
    // 可选:将 ref.current 置为 null
  };
}, []);

常见问题与最佳实践

问题一:Ref.current 为 null 的情况

当 Ref 被绑定到未渲染的元素时,current 属性会返回 null。解决方法包括:

  1. useEffect 中确保 DOM 已加载
  2. 添加条件判断 if (ref.current) { ... }

问题二:过度使用 Refs

虽然 Refs 功能强大,但应避免以下情况:

  • 替代 Props/State 传递简单数据
  • 在渲染过程中直接修改 DOM 样式(优先使用 State)
  • 创建大量 Ref 变量导致代码混乱

最佳实践总结

  1. 最小化使用:仅在必要时(如手动聚焦、第三方库集成)使用 Refs。
  2. 避免副作用:不要在渲染函数中直接操作 Ref.current。
  3. 类型标注:使用 TypeScript 时明确 Ref 的类型:
const inputRef = useRef<HTMLInputElement>(null);

结论

React Refs 是开发者突破虚拟 DOM 限制的重要工具,它像一把精准的手术刀,帮助我们在需要直接操作 DOM 或组件实例时游刃有余。通过本文的学习,读者应能掌握 Refs 的基本用法、核心场景及高级技巧。记住:合理使用 Refs 能提升开发效率,但过度依赖可能破坏 React 的数据流设计原则。建议在日常开发中优先使用 Props 和 State,仅在必要时通过 Refs 实现“最后1%”的功能需求。随着实践的深入,开发者将逐渐掌握 Refs 与 React 核心机制的平衡之道,最终构建出更灵活、高效的 React 应用。

最新发布