React 函数组件中获取 ref 及其组件方法详解

在 React 开发中,ref 是一个非常实用的特性,它允许我们直接访问 DOM 元素或组件实例。对于函数组件而言,由于其没有实例,获取 ref 及组件方法的方式与类组件有所不同。本文将详细介绍在 React 函数组件中如何拿到 ref 以及组件方法,帮助开发者更好地掌握这一知识点。

一、ref 的基本概念

在 React 中,ref 是一个特殊的属性,它允许我们访问 DOM 元素或者 React 组件实例。通常情况下,我们通过 props 和 state 来管理组件的交互,但有时候需要直接操作 DOM 或组件实例,比如获取输入框的值、聚焦输入框、滚动到某个元素等,这时候 ref 就派上用场了。

ref 的作用主要体现在以下几个方面:

  • 访问 DOM 元素:可以直接获取 DOM 元素的属性和方法,进行 DOM 操作,如修改样式、获取尺寸等。

  • 访问组件实例:对于类组件,可以获取组件实例,从而调用组件内部的方法。

  • 管理非受控组件:在非受控组件中,使用 ref 来获取表单元素的值。

二、类组件中 ref 的使用

在了解函数组件中 ref 的使用之前,先来看一下类组件中如何使用 ref,以便进行对比。

2.1 使用 createRef

类组件中可以通过React.createRef()来创建 ref,然后将其赋值给组件的属性,从而获取 DOM 元素或组件实例。

import React, { Component, createRef } from 'react';

class MyInput extends Component {

 constructor(props) {
   super(props);
   this.inputRef = createRef();
 }

 focusInput = () => {
   this.inputRef.current.focus();
 };

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

class ParentComponent extends Component {
 constructor(props) {
   super(props);
   this.myInputRef = createRef();

 }

 handleClick = () => {

   this.myInputRef.current.focusInput();
 };

 render() {
   return (

     <div>
       <MyInput ref={this.myInputRef} />
       <button onClick={this.handleClick}>聚焦输入框</button>
     </div>
   );
 }
}

在上面的例子中,MyInput组件是一个类组件,它通过createRef创建了inputRef来获取输入框的 DOM 元素,并定义了focusInput方法来聚焦输入框。ParentComponent组件通过createRef创建myInputRef并赋值给MyInput组件的ref属性,从而可以调用MyInput组件的focusInput方法。

2.2 使用 findDOMNode

除了createRef,还可以使用ReactDOM.findDOMNode来获取 DOM 元素,但这种方式不推荐使用,因为它可能会导致一些问题,比如在组件未挂载时调用会报错。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class MyComponent extends Component {
 componentDidMount() {
   const domNode = ReactDOM.findDOMNode(this);
   console.log(domNode);
 }
 render() {
   return <div>Hello World</div>;
 }
}

三、函数组件中获取 ref

函数组件没有实例,所以不能像类组件那样直接使用 ref 来获取组件实例。但我们可以通过一些方法来获取函数组件内部的 DOM 元素或让函数组件暴露方法给父组件。

3.1 获取函数组件内部的 DOM 元素

在函数组件中,可以使用useRef钩子来创建 ref,然后将其赋值给 DOM 元素的ref属性,从而获取 DOM 元素。

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

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

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

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

在这个例子中,MyInput是一个函数组件,通过useRef创建了inputRef,并将其赋值给输入框的ref属性。在useEffect钩子中,我们可以通过inputRef.current访问到输入框的 DOM 元素,并调用focus方法使其自动聚焦。

3.2 通过 forwardRef 获取函数组件的 ref

当父组件需要获取函数组件内部的 DOM 元素时,由于函数组件没有实例,直接使用ref属性是不行的。这时候可以使用React.forwardRef来将 ref 转发到函数组件内部的 DOM 元素上。

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

// 使用forwardRef转发ref

const MyInput = forwardRef((props, ref) => {
 return <input ref={ref} />;
});


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

 const handleClick = () => {
   inputRef.current.focus();
 };

 return (
   <div>
     <MyInput ref={inputRef} />
     <button onClick={handleClick}>聚焦输入框</button>
   </div>
 );

}

在上面的代码中,MyInput函数组件通过forwardRef进行了包装,forwardRef接受一个函数作为参数,该函数接收propsref两个参数,并将ref传递给了内部的input元素。这样,ParentComponent组件就可以通过ref获取到MyInput组件内部的input元素,并调用其focus方法。

四、在函数组件中获取组件的方法

有时候,父组件需要调用子组件(函数组件)中的方法。这时候,我们可以结合useImperativeHandleforwardRef来实现。

useImperativeHandle钩子可以自定义暴露给父组件的实例值,即父组件通过ref只能访问到我们允许的方法或属性。

import React, { useRef, forwardRef, useImperativeHandle } from 'react';

const ChildComponent = forwardRef((props, ref) => {
 const [count, setCount] = React.useState(0);

 const increment = () => {
   setCount(count + 1);
 };

 const decrement = () => {
   setCount(count - 1);
 };

 // 自定义暴露给父组件的方法

 useImperativeHandle(ref, () => ({
   increment,
   decrement,
   getCount: () => count

 }));
 return <div>当前计数:{count}</div>

});


function ParentComponent() {

 const childRef = useRef(null);

 const handleIncrement = () => {
   childRef.current.increment();
 };

 const handleDecrement = () => {
   childRef.current.decrement();
 };

 const handleGetCount = () => {
   console.log('当前计数:', childRef.current.getCount());
 };

 return (

   <div>
     <ChildComponent ref={childRef} />
     <button onClick={handleIncrement}>增加</button>
     <button onClick={handleDecrement}>减少</button>
     <button onClick={handleGetCount}>获取计数</button>
   </div>
 );

}

在这个例子中,ChildComponent是一个函数组件,它内部定义了incrementdecrement方法和count状态。通过useImperativeHandle,我们将incrementdecrementgetCount方法暴露给了父组件。ParentComponent组件通过ref获取到ChildComponent暴露的方法,并可以调用这些方法来操作子组件的状态。

五、注意事项

  1. 避免过度使用 ref:React 鼓励通过 props 和 state 来管理组件的交互和状态,过度使用 ref 会使组件之间的耦合度增加,不利于代码的维护和复用。只有在必要的时候,比如需要直接操作 DOM 或调用子组件的特定方法时,才使用 ref。

  2. forwardRef只能用于函数组件:forwardRef是专门为函数组件设计的,用于转发 ref。类组件本身可以直接接收 ref,不需要使用forwardRef

  3. useImperativeHandle应配合forwardRef使用:useImperativeHandle用于自定义暴露给父组件的实例值,它必须与forwardRef一起使用,否则ref参数将为undefined

  4. ref 的.current 属性:在使用 ref 时,要注意ref.current的值在不同阶段的变化。在组件挂载前,ref.currentnull;组件挂载后,ref.current才会指向 DOM 元素或组件实例(对于类组件)。

  5. 不要在函数组件内部的普通 JavaScript 函数中使用 ref:因为 ref 的更新是异步的,在普通函数中可能无法获取到最新的 ref 值。如果需要在函数中使用 ref,应该将其放在useEffect钩子中。

posted @ 2025-07-11 16:43  heshanwan  阅读(107)  评论(0)    收藏  举报