ref、useRef 和 forwardRef

ref、useRef 和 forwardRef之间关系

在 React 中,ref 是一个核心概念,它提供了一种在组件中访问和操作 DOM 节点或 React 元素的方式,是 React 声明式范式之外的一种"命令式"操作手段。

简单来说,refuseRefforwardRef 三者紧密协作,共同解决了"如何直接操作 DOM"以及"如何在组件间传递这种操作能力"的问题。

核心概念解析

1. ref:引用

ref 本身是一个特殊的属性,它可以附加到任何 React 元素上。它的作用是创建一个"引用",这个引用对象有一个 current 属性,React 会自动将 current 指向对应的 DOM 节点或组件实例。

2. useRef:创建引用的 Hook

useRef 是一个 React Hook,主要在函数组件中使用。它有两个核心用途:

  • 访问 DOM 节点 :创建一个引用对象,并将其通过 ref 属性绑定到 JSX 元素上,从而在组件中获取对该 DOM 元素的直接引用。
  • 存储可变值 :创建一个在组件整个生命周期内都保持不变的"容器",用于存储任何可变值(如定时器ID、上一个状态值等)。修改 useRef 返回对象的 current 属性不会触发组件的重新渲染 ,这是它与 useState 的关键区别。

示例:使用 useRef 实现输入框自动聚焦

jsx 复制代码
import { useRef, useEffect } from 'react';

function InputField() {
  // 1. 创建 ref 对象
  const inputRef = useRef(null);

  useEffect(() => {
    // 2. 组件挂载后,通过 .current 访问并操作 DOM
    inputRef.current.focus();
  }, []);

  // 3. 将 ref 对象绑定到 input 元素
  return <input ref={inputRef} type="text" />;
}
3. forwardRef:转发引用的工具

forwardRef 是一个高阶函数,用于将 ref 从父组件转发到子组件内部的某个 DOM 元素上。

为什么需要它?

默认情况下,函数组件没有实例,因此无法直接接收 ref。如果你在父组件中尝试将一个 ref 传给一个普通的函数组件,这个 ref 会"消失",无法到达组件内部。forwardRef 就是为了解决这个问题而生的,它像一个"桥梁",打通了组件边界,让父组件的 ref 能够穿透子组件,直接绑定到子组件内部的特定 DOM 元素上。

示例:使用 forwardRef 让父组件控制子组件的 DOM

jsx 复制代码
import { forwardRef, useRef } from 'react';

// 1. 子组件使用 forwardRef 包装,并接收 ref 作为第二个参数
const FancyInput = forwardRef((props, ref) => {
  return (
    <div>
      <label>{props.label}</label>
      {/* 2. 将接收到的 ref 绑定到内部的 input 元素 */}
      <input ref={ref} {...props} />
    </div>
  );
});

function ParentComponent() {
  // 3. 父组件创建 ref
  const inputRef = useRef(null);

  const handleClick = () => {
    // 4. 父组件可以直接操作子组件内部的 input 元素
    inputRef.current.focus();
  };

  return (
    <div>
      <FancyInput ref={inputRef} label="用户名:" />
      <button onClick={handleClick}>聚焦输入框</button>
    </div>
  );
}

关联与区别

useRefforwardRef 通常协同工作,但它们扮演的角色截然不同。

  • useRef 负责创建引用的容器。
  • forwardRef 负责传递这个引用容器。

它们的协作流程可以概括为:父组件用 useRef 创建 ref → 通过 ref 属性传给子组件 → 子组件用 forwardRef 接收并转发 ref → 最终将 ref 绑定到内部的 DOM 元素上。

下表清晰地展示了三者的区别:

特性 ref (属性) useRef (Hook) forwardRef (函数)
核心作用 附加到 JSX 元素上,用于获取其引用。 在函数组件中创建一个可变的引用对象。 转发 ref,使其能穿透函数组件。
使用场景 任何 React 元素(DOM 或组件)。 函数组件内部,用于访问 DOM 或存储可变值。 封装可复用的函数组件,并需要暴露其内部 DOM 时。
主要目的 建立引用关系。 提供一个持久化的引用容器。 打通组件边界,实现引用的透传。

总而言之,ref 是建立引用的机制,useRef 是在函数组件中实现这一机制的工具,而 forwardRef 则是解决函数组件无法直接接收 ref 这一限制的"传送门"。


forwardRef<HTMLButtonElement, Props>解释

forwardRef<HTMLButtonElement, Props>TypeScript 泛型语法,指定这个组件转发的 ref 类型和 props 类型。

拆解

tsx 复制代码
forwardRef<HTMLButtonElement, Props>(renderFn)
           ↑                ↑
        ref 的类型      props 的类型
泛型参数 含义
HTMLButtonElement 暴露给外部的 ref 指向什么 DOM 元素
Props 组件接收什么 props

例子

tsx 复制代码
interface Props {
    label: string;
    onClick: () => void;
}

// 这个组件的 ref 会指向 <button> DOM 元素
const Button = forwardRef<HTMLButtonElement, Props>((props, ref) => {
    return (
        <button ref={ref} onClick={props.onClick}>
            {props.label}
        </button>
    );
});

// 外部使用
const ref = useRef<HTMLButtonElement>(null);
<Button ref={ref} label='点我' />;

为什么需要指定

tsx 复制代码
// 不指定 - 外部不知道 ref 指向什么
const A = forwardRef((props, ref) => ...);

// 指定 - 外部知道 ref 指向 HTMLButtonElement
const B = forwardRef<HTMLButtonElement, Props>((props, ref) => ...);

一句话forwardRef<RefType, PropsType> 告诉 TypeScript:这个组件的 ref 指向 RefType,接收的 props 是 PropsType


相关概念

ForwardRefExoticComponent

"Exotic" 是 React 内部术语,表示一种"不普通"的组件。forwardRefmemo 包装后的组件类型就是 ForwardRefExoticComponent

普通组件 Exotic 组件
FC, Component forwardRef, memo 包装后的类型
RefAttributes

RefAttributes<T> 是 React 用来给组件附加 ref 类型信息的内置类型。

tsx 复制代码
interface RefAttributes<T> {
    ref?: Ref<T> | null; // ref 接受的值
}

RefAttributes<T> 就是让组件类型带上 ref 能力的标记类型,通常配合 forwardRef 使用。

相关推荐
breeze jiang10 分钟前
React + WebGPU 在浏览器运行 DeepSeek:从 Worker 通信到流式生成
前端·javascript·react.js
a11177621 分钟前
家居生成器 Cartoon 开源项目 3D web
前端·开源
小林ixn27 分钟前
前端卡顿终结者:用 useRef 把 Web Worker 请进 React 项目
前端·react.js·前端框架
sunly_28 分钟前
React 三个重要概念:Ref、Props、State 详解
前端·javascript·react.js
weixin_4316004429 分钟前
前端对接 SSE 的两种常见方式
前端·后端·学习·ai·sse·nest.js
Mh35 分钟前
如何使用GSAP实现一个 `pinned` 滚动楼层叙事?
前端·javascript·css
Larcher7 小时前
React Router 不只是页面跳转:从 SPA 路由到权限守卫的完整实践
javascript·后端
Larcher7 小时前
大模型为什么每次回答都不一样?一文搞懂 Temperature、Top K 与 Top P
javascript·后端
用户938515635079 小时前
从零在浏览器里跑 DeepSeek-R1:WebGPU + Transformer.js 全链路实战
前端·设计模式·typescript
CodeSheep10 小时前
稚晖君公司人事大变动,来了!
前端·后端·程序员