什么是 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!
也就是说,它的排面要给足了!
例子
接下来让我们用一个例子来充分感受一下吧!
我将要创建两个按钮,其中一个按钮'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.
接下来是程序运行结果:
我们可以看到它报错了,因为函数组件默认不支持ref属性,此时buttonRef谁都没有绑定上,所以ref的值目前为null,控制台会报错。
接下来,是加了forwardRef的:
{/*只修改这一句*/}
const LogButton = forwardRef(function LogButton(props, ref) {
return (
<button ref={ref} className="Button">
{props.children}
</button>
);
})
这里就成功聚焦了,这就是forwardRef的魅力!它能允许我们操纵子组件的DOM!
React19 以后,废弃?
在 React19 以后,React 官方声明开始逐步废弃forwardRef。
但是取而代之的是,他把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引用buttonRef和inputRef,传入给了<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元素,事情全部变得简单了起来!