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

相关推荐
薛定喵的谔22 分钟前
我开源了一个精致的 Next.js 博客模板:Skyplume
前端·前端框架·next.js
张龙6871 小时前
构建生产级 AI Agent:工具调用与记忆架构实战指南
前端
YFF菲菲兔1 小时前
useState 源码解析
react.js
kyriewen2 小时前
2026 年了,还在用 Node.js?Bun 迁移实战:20 分钟搞定,附踩坑记录
前端·javascript·node.js
青山Coding4 小时前
Cesium应用(八):物体运动的实现思路
前端·cesium
用户41659673693554 小时前
Android WebView 加载 file:// 离线页面调试教程
android·前端
Asmewill4 小时前
curl命令学习笔记一
前端
我是一只快乐的小螃蟹4 小时前
1.2 ArrayList 源码解析
前端
星栈4 小时前
我用 Rust + Dioxus 做了个全栈跨平台笔记应用:再把新建、编辑和交付补上
前端·rust·前端框架
我是一只快乐的小螃蟹4 小时前
1.1 HashMap (JDK1.8) 源码解析
前端