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 使用。

相关推荐
energy_DT2 小时前
2026年海上钻井平台数字孪生平台:引领海洋能源数字化转型
前端
Eric_见嘉2 小时前
在职前端 Agent 配置分享
前端·后端·agent
柚子8162 小时前
break跳出语句块的神奇技巧
前端·javascript
ejinxian4 小时前
Rust GUI框架Azul与Electron、WebView2
前端·javascript·electron
IT_陈寒4 小时前
Vue的v-for里用index当key,我被自己坑惨了
前端·人工智能·后端
代码不加糖5 小时前
0基础搭建前后端分离项目:实现菜单与界面左右布局
java·前端·javascript·mysql·elementui·mybatis
zhensherlock5 小时前
Protocol Launcher 系列:Tally 快速计数器的深度集成
前端·javascript·typescript·node.js·自动化·github·js
AC赳赳老秦5 小时前
OpenClaw权限管理实操:团队共享Agent,设置操作权限,保障数据安全
服务器·开发语言·前端·javascript·excel·deepseek·openclaw
光影少年6 小时前
Polyline 组件如何绘制渐变区域?
前端·javascript·掘金·金石计划