forwardRef
forwardRef允许您的组件通过ref将DOM节点暴露给父组件。
const SomeComponent = forwardRef(render)参考
forwardRef(render)
调用forwardRef()允许您的组件接收ref并将其转发到子组件
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
// ...
});参数
render:您的组件的渲染函数。React 使用props和ref调用此函数(您的组件从父组件接收到的)。您返回的JSX将是组件的输出。
返回值
forwardRef返回一个可以在JSX中渲染的React组件。与定义为普通函数的React组件不同,由forwardRef返回的组件也能够接收ref属性。
注意事项
- 在严格模式下,React将调用您的render函数两次,以帮助您查找意外的非纯函数。这仅是开发环境的行为,不会影响生产环境。如果您的render函数是纯函数(它应该是),则这不会影响组件的逻辑。其中一个调用的结果将被忽略。
render 函数
forwardRef 接受一个渲染函数作为参数。React 使用 props 和 ref 调用此函数。
const MyInput = forwardRef(function MyInput(props, ref) {
return (
<label>
{props.label}
<input ref={ref} />
</label>
);
});参数
-
props:父组件传递的属性。 -
ref:父组件传递的ref属性。ref可以是对象或函数。如果父组件没有传递 ref,则它将为null。您应该将收到的ref传递给另一个组件,或者将其传递给useImperativeHandle。
返回值
forwardRef 返回一个可以在 JSX 中渲染的 React 组件。与定义为普通函数的 React 组件不同,forwardRef 返回的组件能够接收 ref 属性。
用法
将 DOM 节点暴露给父组件
默认情况下,每个组件的 DOM 节点都是私有的。但是,有时将 DOM 节点暴露给父组件非常有用——例如,允许对其进行聚焦。要选择加入,请将组件定义包装到 forwardRef() 中。
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
const { label, ...otherProps } = props;
return (
<label>
{label}
<input {...otherProps} />
</label>
);
});您将在 props 之后作为第二个参数接收一个 ref。将其传递给要公开的 DOM 节点。
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
const { label, ...otherProps } = props;
return (
<label>
{label}
<input {...otherProps} ref={ref} />
</label>
);
});这允许父 Form 组件访问 <input> DOM 节点,该节点由 MyInput 公开。
function Form() {
const ref = useRef(null);
function handleClick() {
ref.current.focus();
}
return (
<form>
<MyInput label="Enter your name:" ref={ref} />
<button type="button" onClick={handleClick}>
Edit
</button>
</form>
);
}此 Form 组件 传递 ref 给 MyInput。MyInput 组件将该 ref 转发给浏览器 <input> 标记。因此,Form 组件可以访问该 <input> DOM 节点并调用 focus()。
请记住,将 ref 暴露给组件内部的 DOM 节点会使以后更改组件的内部结构变得更加困难。您通常会从可重用的低级组件(如按钮或文本输入)公开 DOM 节点,但不会对应用程序级组件(如头像或评论)执行此操作。
示例 1的 2: 聚焦文本输入框
点击按钮将聚焦输入框。Form 组件定义了一个 ref 并将其传递给 MyInput 组件。MyInput 组件将该 ref 转发给浏览器 <input>。这允许 Form 组件聚焦 <input>。
import { useRef } from 'react'; import MyInput from './MyInput.js'; export default function Form() { const ref = useRef(null); function handleClick() { ref.current.focus(); } return ( <form> <MyInput label="Enter your name:" ref={ref} /> <button type="button" onClick={handleClick}> Edit </button> </form> ); }
通过多个组件转发 ref
您可以将 ref 转发到您自己的组件(例如 MyInput),而不是转发到 DOM 节点。
const FormField = forwardRef(function FormField(props, ref) {
// ...
return (
<>
<MyInput ref={ref} />
...
</>
);
});如果该 MyInput 组件将 ref 转发到其 <input>,则指向 FormField 的 ref 将为您提供该 <input>。
function Form() {
const ref = useRef(null);
function handleClick() {
ref.current.focus();
}
return (
<form>
<FormField label="Enter your name:" ref={ref} isRequired={true} />
<button type="button" onClick={handleClick}>
Edit
</button>
</form>
);
}Form 组件定义了一个 ref 并将其传递给 FormField。FormField 组件将该 ref 转发给 MyInput,后者又将其转发到浏览器 <input> DOM 节点。这就是 Form 访问该 DOM 节点的方式。
import { useRef } from 'react'; import FormField from './FormField.js'; export default function Form() { const ref = useRef(null); function handleClick() { ref.current.focus(); } return ( <form> <FormField label="Enter your name:" ref={ref} isRequired={true} /> <button type="button" onClick={handleClick}> Edit </button> </form> ); }
公开命令式句柄而不是 DOM 节点
您可以公开一个自定义对象(称为*命令式句柄*),它具有一组更受限制的方法,而不是公开整个 DOM 节点。为此,您需要定义一个单独的 ref 来保存 DOM 节点。
const MyInput = forwardRef(function MyInput(props, ref) {
const inputRef = useRef(null);
// ...
return <input {...props} ref={inputRef} />;
});将您收到的 ref 传递给 useImperativeHandle 并指定您想要公开给 ref 的值。
import { forwardRef, useRef, useImperativeHandle } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => {
return {
focus() {
inputRef.current.focus();
},
scrollIntoView() {
inputRef.current.scrollIntoView();
},
};
}, []);
return <input {...props} ref={inputRef} />;
});如果某个组件获得了指向 MyInput 的 ref,它将只接收您的 { focus, scrollIntoView } 对象,而不是 DOM 节点。这使您可以将关于 DOM 节点的公开信息限制到最低限度。
import { useRef } from 'react'; import MyInput from './MyInput.js'; export default function Form() { const ref = useRef(null); function handleClick() { ref.current.focus(); // This won't work because the DOM node isn't exposed: // ref.current.style.opacity = 0.5; } return ( <form> <MyInput placeholder="Enter your name" ref={ref} /> <button type="button" onClick={handleClick}> Edit </button> </form> ); }
故障排除
我的组件被 forwardRef 包装,但是指向它的 ref 始终为 null
这通常意味着您忘记实际使用收到的 ref。
例如,此组件没有对它的 ref 做任何操作。
const MyInput = forwardRef(function MyInput({ label }, ref) {
return (
<label>
{label}
<input />
</label>
);
});要解决此问题,请将 ref 传递到可以接受 ref 的 DOM 节点或另一个组件。
const MyInput = forwardRef(function MyInput({ label }, ref) {
return (
<label>
{label}
<input ref={ref} />
</label>
);
});如果某些逻辑是有条件的,那么指向 MyInput 的 ref 也可能为 null。
const MyInput = forwardRef(function MyInput({ label, showInput }, ref) {
return (
<label>
{label}
{showInput && <input ref={ref} />}
</label>
);
});如果 showInput 为 false,则 ref 不会转发到任何节点,并且指向 MyInput 的 ref 将保持为空。如果条件隐藏在另一个组件(例如本例中的 Panel)中,则这一点很容易被忽略。
const MyInput = forwardRef(function MyInput({ label, showInput }, ref) {
return (
<label>
{label}
<Panel isExpanded={showInput}>
<input ref={ref} />
</Panel>
</label>
);
});