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
prop。
注意事项
- 在严格模式下,React 将会调用您的渲染函数两次,以便帮助您发现意外的副作用。这只是开发环境中的行为,不会影响生产环境。如果您的渲染函数是纯函数(应该是这样),这应该不会影响组件的逻辑。其中一次调用的结果将被忽略。
render
函数
聚焦文本输入的示例 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
传递给 DOM 节点或可以接受 ref 的其他组件
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>
);
});