0

0

深入理解React中Refs、DOM组件与Ref转发机制

花韻仙語

花韻仙語

发布时间:2025-10-12 12:34:01

|

293人浏览过

|

来源于php中文网

原创

深入理解react中refs、dom组件与ref转发机制

本文旨在深入探讨React中Refs、DOM组件以及Ref转发(Ref Forwarding)机制,特别是澄清在React文档中“DOM组件”一词的含义及其与类组件实例的区别。我们将解析Refs如何用于访问DOM节点或组件实例,以及Ref转发在跨组件层级传递Refs时的重要作用,并提供示例代码以加深理解。

React Refs概述

在React应用开发中,我们通常通过声明式的方式来构建用户界面。然而,在某些特定场景下,我们需要直接与底层DOM节点或React组件实例进行交互,例如管理焦点、触发动画、集成第三方DOM库等。React提供了Refs(引用)机制来满足这些需求。

Refs允许我们获取对React元素背后DOM节点或组件实例的直接引用。它们是逃离React声明式范式的“后门”,应谨慎使用,仅在必要时才采用。

理解“DOM组件”与Refs的直接使用

在React的语境中,当文档提到“DOM组件”时,它通常指的是原生的HTML元素,例如<button>, <input>, <div>等。我们可以直接将Refs附加到这些DOM组件上,React会将该Ref指向对应的DOM节点。

例如,如果我们有一个简单的按钮:

import React, { useRef, useEffect } from 'react';

function MyButton() {
  const buttonRef = useRef(null);

  useEffect(() => {
    if (buttonRef.current) {
      console.log('按钮DOM节点:', buttonRef.current);
      buttonRef.current.focus(); // 直接操作DOM
    }
  }, []);

  return (
    <button ref={buttonRef}>
      点击我
    </button>
  );
}

在这个例子中,buttonRef直接引用了<button>这个DOM元素。

Ref转发(Ref Forwarding)机制

当Refs需要从父组件传递到一个自定义的子组件,并最终指向该子组件内部的某个DOM节点或另一个组件实例时,就需要使用Ref转发。这是因为默认情况下,自定义的函数组件或类组件不会将Refs直接传递给其内部的子元素。

为什么需要Ref转发?

考虑以下场景:一个父组件需要获取子组件内部的某个DOM元素的引用。如果子组件是一个函数组件,它没有实例,因此Ref无法直接附加到它上面。如果子组件是一个类组件,Ref会指向该类组件的实例,而不是其内部的DOM元素。为了让父组件能够“穿透”子组件,直接访问到子组件内部的特定元素,Ref转发应运而生。

Ref转发的实现

React提供了React.forwardRef函数来实现Ref转发。它接收一个渲染函数作为参数,这个渲染函数会接收props和ref作为参数。

GentleAI
GentleAI

GentleAI是一个高效的AI工作平台,为普通人提供智能计算、简单易用的界面和专业技术支持。让人工智能服务每一个人。

下载
import React, { useRef, useEffect } from 'react';

// 假设我们有一个FancyButton组件,它是一个函数组件
const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

function ParentComponent() {
  const buttonRef = useRef(null);

  useEffect(() => {
    if (buttonRef.current) {
      console.log('FancyButton内部的DOM节点:', buttonRef.current);
      buttonRef.current.focus();
    }
  }, []);

  return (
    <FancyButton ref={buttonRef}>
      提交
    </FancyButton>
  );
}

在这个例子中,ParentComponent将buttonRef传递给FancyButton。通过React.forwardRef,FancyButton能够接收到这个ref,并将其附加到它内部的<button>元素上。最终,buttonRef.current将指向FancyButton内部的实际DOM <button>节点。

Ref转发到类组件实例

React文档中提到:“Ref转发不仅限于DOM组件。你也可以将Refs转发给类组件实例。” 这句话的含义是:

  1. “DOM组件” 在这里指的是Ref最终指向的原生HTML元素。
  2. “类组件实例” 指的是Ref转发的最终目标可以是自定义的类组件的实例,而不仅仅是原生DOM元素。

这意味着,当我们将Ref转发到一个自定义的类组件时,该Ref将持有该类组件的实例,而非其内部的某个DOM节点。通过这个实例,我们可以调用类组件内部定义的方法或访问其属性。

考虑以下示例,一个父组件需要访问子类组件的实例:

import React, { useRef, useEffect } from 'react';

// 子类组件
class ClassComponent extends React.Component {
  focusInput() {
    console.log('ClassComponent: 内部方法被调用');
    // 假设这里有一个内部的input,我们可以通过内部ref来操作它
    // this.internalInputRef.current.focus();
  }

  render() {
    // innerRef是父组件通过forwardRef传递过来的ref
    return (
      <input
        value={this.props.value}
        onChange={this.props.onChange}
        ref={this.props.innerRef} // 将转发的ref附加到内部input
        placeholder="这是一个类组件的输入框"
      />
    );
  }
}

// 使用React.forwardRef将ref转发到ClassComponent
const Input = React.forwardRef(function (props, ref) {
  // 注意:这里将父组件传递的ref作为props(innerRef)传递给ClassComponent
  // 这样ClassComponent就可以将这个ref附加到它内部的DOM元素上
  return <ClassComponent {...props} innerRef={ref} />;
});

function App() {
  const classComponentRef = useRef(null); // 这个ref将指向ClassComponent的实例

  useEffect(() => {
    if (classComponentRef.current) {
      console.log('获取到的Ref指向:', classComponentRef.current);
      // 如果ref指向ClassComponent的实例,我们可以调用其方法
      // 注意:上面的ClassComponent将innerRef附加到了input上,
      // 所以classComponentRef.current将是input的DOM节点。
      // 如果要获取ClassComponent的实例,ClassComponent需要将它自身的实例暴露出来,
      // 或者forwardRef直接返回ClassComponent本身(不推荐,通常用于DOM节点)。

      // 为了演示获取类组件实例,我们需要稍微修改ClassComponent和forwardRef的用法:
      // ClassComponent的render方法不将ref附加到DOM,而是forwardRef直接返回ClassComponent
      // 如下面的修正:
    }
  }, []);

  const handleFocus = () => {
    if (classComponentRef.current) {
      // 假设ClassComponent内部有一个方法可以触发焦点
      // 如果ref指向DOM节点,可以直接调用focus()
      classComponentRef.current.focus();
    }
  };

  return (
    <div>
      <h3>Ref转发到类组件内部的DOM节点</h3>
      <Input ref={classComponentRef} value="Hello" onChange={() => {}} />
      <button onClick={handleFocus}>聚焦输入框</button>
      <p>
        在这种情况下,<code>classComponentRef.current</code> 将指向 <code>ClassComponent</code> 内部的 <code><input></code> DOM节点。
      </p>
    </div>
  );
}

// 修正:如果Ref要指向类组件实例,而不是其内部DOM,
// forwardRef的第二个参数(ref)不应直接传递给内部DOM元素。
// 而是ClassComponent本身需要被包装,并且ref指向ClassComponent的实例。
// 然而,React推荐Ref转发的最终目标是DOM节点。
// 如果确实需要访问类组件实例,通常直接将ref附加到类组件上即可,
// 但这不涉及forwardRef。forwardRef主要是为了将ref“穿透”一个组件。

// 重新理解原文档的意图:
// "Ref forwarding is not limited to DOM components. You can forward refs to class component instances, too."
// 这句话的重点在于,通过forwardRef,你可以将ref传递给一个子组件,
// 这个子组件可以是最终渲染DOM的组件(如FancyButton),
// 也可以是另一个自定义的类组件。当ref最终到达一个类组件时,
// 如果你将这个ref直接附加到该类组件的*实例*上(而不是它内部的DOM),
// 那么这个ref就会指向该类组件的实例。
// 但通常,forwardRef的目的是为了访问子组件内部的DOM节点。

// 让我们用一个更清晰的例子来演示如果ref直接指向类组件实例:
class ChildClassComponent extends React.Component {
  sayHello() {
    console.log('Hello from ChildClassComponent instance!');
  }
  render() {
    return <p>我是子类组件</p>;
  }
}

// 如果一个父组件直接使用ChildClassComponent,ref会指向其实例
function ParentWithClassInstanceRef() {
  const childRef = useRef(null);

  useEffect(() => {
    if (childRef.current) {
      console.log('Ref指向类组件实例:', childRef.current);
      childRef.current.sayHello(); // 调用实例方法
    }
  }, []);

  return <ChildClassComponent ref={childRef} />;
}

// forwardRef的场景通常是:父组件 -> 函数组件 (forwardRef) -> 类组件 (被包装) -> DOM
// 例如:
const ForwardedClassComponentWrapper = React.forwardRef((props, ref) => {
  // ref在这里会指向ClassComponent的实例
  return <ClassComponent {...props} innerRef={ref} />; // 这里innerRef仍然指向DOM
  // 如果要让ref指向ClassComponent实例,ClassComponent本身不应该有innerRef,
  // 而是forwardRef直接返回ClassComponent,但这不符合forwardRef的典型用法。

  // 更准确的理解是:
  // forwardRef可以把ref传递给一个子组件,这个子组件可以是函数组件,也可以是类组件。
  // 如果子组件是类组件,并且你把ref直接附加到这个类组件上(而不是它的内部DOM),
  // 那么ref就会指向这个类组件的实例。

  // 让我们调整ClassComponent的示例,使其Ref真正指向类组件实例
});

// 最终示例:Ref转发到类组件内部的DOM元素
// 这是最常见且推荐的forwardRef用法
function TutorialApp() {
  const inputRef = useRef(null);

  useEffect(() => {
    if (inputRef.current) {
      console.log('通过Ref转发获取到输入框DOM节点:', inputRef.current);
      inputRef.current.focus();
    }
  }, []);

  return (
    <div>
      <h2>Ref转发到类组件内部的DOM元素</h2>
      <p>
        通过 <code>React.forwardRef</code>,我们可以将父组件的 Ref 传递给一个中间组件(这里是 <code>InputWrapper</code>),
        然后由这个中间组件将 Ref 进一步传递给其内部的类组件 <code>ClassComponent</code>,
        最终 <code>ClassComponent</code> 将这个 Ref 附加到它所渲染的 <code><input></code> DOM 元素上。
        这样,父组件的 Ref 就可以直接访问到最深层的 DOM 节点。
      </p>
      <Input ref={inputRef} value="Hello World" onChange={() => {}} />
      <button onClick={() => inputRef.current && inputRef.current.select()}>
        选中输入框内容
      </button>
    </div>
  );
}

export default TutorialApp;

在上面的 Input 组件的例子中,React.forwardRef 接收到的 ref 被作为 innerRef prop 传递给 ClassComponent。ClassComponent 随后将这个 innerRef 附加到它渲染的 <input> 元素上。因此,当你在 App 组件中访问 inputRef.current 时,你实际上得到的是 <input> 元素的 DOM 节点,而不是 ClassComponent 的实例。

如果真的需要获取 ClassComponent 的实例,而不是它内部的 DOM 节点,那么 ClassComponent 应该是一个直接的子组件,并且 ref 直接附加到它上面(不通过 forwardRef 穿透)。forwardRef 的主要目的是为了穿透组件层级,访问到内部的 DOM 节点。

总结一下文档的含义: “Ref转发不限于DOM组件(即直接将Ref指向原生HTML元素)。你也可以将Refs转发给类组件实例。” 这句话的深层含义是:

  1. forwardRef 可以让你将一个Ref从父组件传递下去,最终指向一个原生的DOM元素(这是最常见的用法)。
  2. forwardRef 也可以让你将一个Ref从父组件传递下去,最终指向一个子类组件的实例。这通常发生在子组件本身就是一个类组件,并且你希望通过Ref获取到该类组件的实例,以便调用其方法或访问其内部状态(尽管这通常被认为是反模式,应优先使用props和state)。

注意事项与最佳实践

  • Refs的滥用: 避免过度使用Refs。在大多数情况下,通过props和state进行数据流管理是更推荐的React范式。Refs主要用于那些无法通过声明式方式实现的场景。
  • Refs与函数组件: 函数组件默认没有实例,因此不能直接附加Refs。必须使用React.forwardRef才能将Refs传递给函数组件,并由其转发到内部的DOM节点或类组件。
  • Refs的生命周期: Refs在组件挂载后才可用,在组件卸载时变为null。
  • Refs的类型: 当Ref指向DOM元素时,ref.current是DOM节点;当Ref指向类组件时,ref.current是类组件的实例。

结论

Refs是React中一个强大的工具,用于直接访问DOM节点或组件实例。Ref转发机制,通过React.forwardRef,解决了Refs在组件层级间传递的问题,使得父组件能够“穿透”子组件,访问到深层级的DOM元素或类组件实例。理解“DOM组件”在文档中的具体语境以及Ref转发的灵活性,对于编写健壮和高效的React应用至关重要。正确地使用Refs和Ref转发,能够帮助我们处理复杂的交互和集成需求,同时保持React应用的声明式特性。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

254

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

1089

2024.03.01

c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

254

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

1089

2024.03.01

DOM是什么意思
DOM是什么意思

dom的英文全称是documentobjectmodel,表示文件对象模型,是w3c组织推荐的处理可扩展置标语言的标准编程接口;dom是html文档的内存中对象表示,它提供了使用javascript与网页交互的方式。想了解更多的相关内容,可以阅读本专题下面的文章。

4348

2024.08.14

点击input框没有光标怎么办
点击input框没有光标怎么办

点击input框没有光标的解决办法:1、确认输入框焦点;2、清除浏览器缓存;3、更新浏览器;4、使用JavaScript;5、检查硬件设备;6、检查输入框属性;7、调试JavaScript代码;8、检查页面其他元素;9、考虑浏览器兼容性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

197

2023.11.24

Python异步编程与Asyncio高并发应用实践
Python异步编程与Asyncio高并发应用实践

本专题围绕 Python 异步编程模型展开,深入讲解 Asyncio 框架的核心原理与应用实践。内容包括事件循环机制、协程任务调度、异步 IO 处理以及并发任务管理策略。通过构建高并发网络请求与异步数据处理案例,帮助开发者掌握 Python 在高并发场景中的高效开发方法,并提升系统资源利用率与整体运行性能。

37

2026.03.12

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

136

2026.03.11

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

47

2026.03.10

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 6万人学习

国外Web开发全栈课程全集
国外Web开发全栈课程全集

共12课时 | 1万人学习

React核心原理新老生命周期精讲
React核心原理新老生命周期精讲

共12课时 | 1.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号