🔥前端面试必问:forwardRef 你用对了吗?🔥

214 阅读4分钟

什么是 forwardRef ?

forwardRef 是 React 中的一个高级特性,主要用于处理高阶组件(Higher-Order Components, HOCs)或自定义 Hook 时,确保 ref 能够正确地传递给最终的 DOM 元素或者 class 组件。 这对于需要直接操作底层组件实例的情况非常有用,比如聚焦输入框、触发动画等。

为什么会有forwardRef ?

还记得我们之前的useRef吗?它可以绑定到某个DOM元素上供我们操纵,我们可以改变它的属性,可以聚焦.....但是呢,一般这些都会被封装进子组件的,我们的useRef不能直接操纵子组件的DOM元素,如果直接将 useRef 应用到 HOC 上,useRef会绑定失败,因为函数默认不支持ref的绑定,此时它的值是null,而执行后控制台会报错。forwardRef 就是为了解决这个问题而设计的。

forwardRef 怎么用?

const MyInput = forwardRef(function MyInput(props, ref) {  

return (  

<label>  
{props.label}  
<input ref={ref} />  

</label>  
);  
});

forwardRef(render),接收一个渲染函数(返回值是一个组件),这个函数中有两类参数,一类是普通的参数props,一类是ref

这个时候可能有人要问了:“为什么ref不在props中呢?它也是个参数啊!”

其实呢,ref确实是参数,但是!引用ref要作为参数传进子组件必须要显式声明,它传进子组件的名字只能叫ref

也就是说,它的排面要给足了!

1112.jpg

例子

接下来让我们用一个例子来充分感受一下吧!

我将要创建两个按钮,其中一个按钮'Focus the button'是子组件:

不用forwardRef时:

import React from "react";
import { forwardRef } from 'react';

const LogButton = function LogButton(props, ref) {
    return (
        <button ref={ref} className="Button">
            {props.children}
        </button>
    );
}


const ForwardRef = () => {
    const buttonRef = React.useRef(null);

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

    return (
        <div>
            <LogButton ref={buttonRef}>Click</LogButton>
            <button onClick={handleClick}>Focus the button</button>
        </div>
    );
}

export default ForwardRef;

我们可以看到,我们将buttonRef作为参数传递给了<LogButton>,在<LogButton>中这个值的名字叫ref.

接下来是程序运行结果:

ezgif.com-video-to-gif-converter.gif

我们可以看到它报错了,因为函数组件默认不支持ref属性,此时buttonRef谁都没有绑定上,所以ref的值目前为null,控制台会报错。

接下来,是加了forwardRef的:

{/*只修改这一句*/}
const LogButton = forwardRef(function LogButton(props, ref) {
    return (
        <button ref={ref} className="Button">
            {props.children}
        </button>
    );
})

bandicam2025-08-2221-54-33-921-ezgif.com-video-to-gif-converter.gif

这里就成功聚焦了,这就是forwardRef的魅力!它能允许我们操纵子组件的DOM!

React19 以后,废弃?

在 React19 以后,React 官方声明开始逐步废弃forwardRef

image.png

但是取而代之的是,他把ref变成了一个普通形参传递,我们可以直接在父组件中使用,直接就可以操作子组件,如下:

import React, { useEffect } from "react";
import { forwardRef } from 'react';

const LogButton = function LogButton(props) {
    const { ref1, ref2 } = props;

    return (
        <>
            <button ref={ref1} className="Button">
                Click,Children
            </button>
            <input id='001' ref={ref2} placeholder="输入内容" />

        </>
    );
}


const ForwardRef = () => {
    const buttonRef = React.useRef(null);
    const inputRef = React.useRef(null);

    useEffect(() => {
        console.log(buttonRef.current);


    }, [])
    const handleClick = () => {
        buttonRef.current.focus();
    }
    const handleInputClick = () => {
        inputRef.current.focus();
    }

    return (
        <div>
            <LogButton ref1={buttonRef} ref2={inputRef} />
            <button onClick={handleClick}>Focus the button</button>
            <button onClick={handleInputClick}>Focus the input</button>

        </div>
    );
}

export default ForwardRef;

在这里我们在父组件中定义了两个ref引用buttonRefinputRef,传入给了<LogButton>,之后进行了解构,将其绑定到了子元素上,这样父组件一样能够引用子组件的DOM元素。

useImperativeHandle

ref还能被绑定到useImperativeHandle中,这个Hooks函数可以允许暴露出子组件的一些方法,从而让父组件去使用它。

经常见的场景就是:我们封装了一个输入密码的组件,假设里面有两个Input框,第一个为设置密码,第二个为确定密码,当这个组件作为子组件被使用的时候,它的密码需要被父组件拿出来使用,并通过一定的函数向某个url发送给后端。 而由于组件是封闭的,我们没办法直接拿到确定的信息,所以就需要在子组件定义这么一个函数来获取密码,并暴露给父组件。

它的语法如下:

useImperativeHandle(ref, createHandle, [dependencies]);
  • ref:从 forwardRef 接收到的 ref 对象。
  • createHandle:一个函数,它的返回值会成为父组件 ref.current 的值。你可以在这个函数里返回一个包含你想暴露的方法的对象。
  • [dependencies] (可选) :一个依赖数组。如果数组中的任何值发生变化,createHandle 函数会重新执行,生成新的句柄(handle)。

总结

useRef可以让我们能够应用本组件的DOM元素,而forwardRef可以让我们应用子组件的DOM元素,我们只需要将子组件的渲染函数作为参数传递进forwardRef即可,但是到了React19版本之后,forwardRef遭到了废弃,ref也变成了一个普通的参数可以被传递进子组件,父组件可以顺利利用本组件传递的ref操作子组件的DOM元素,事情全部变得简单了起来!