forwardRef
允许你的组件使用 引用 向父组件公开 DOM 节点
¥forwardRef
lets your component expose a DOM node to parent component with a ref.
const SomeComponent = forwardRef(render)
参考
¥Reference
forwardRef(render)
调用 forwardRef()
让你的组件接收引用并将其转发给子组件:
¥Call forwardRef()
to let your component receive a ref and forward it to a child component:
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
// ...
});
参数
¥Parameters
-
render
:组件的渲染函数。React 使用你的组件从其父级接收到的属性 和ref
调用此函数。你返回的 JSX 将是你组件的输出。¥
render
: The render function for your component. React calls this function with the props andref
that your component received from its parent. The JSX you return will be the output of your component.
返回
¥Returns
forwardRef
返回一个可以在 JSX 中渲染的 React 组件。与定义为普通函数的 React 组件不同,forwardRef
返回的组件也能够接收 ref
属性。
¥forwardRef
returns a React component that you can render in JSX. Unlike React components defined as plain functions, a component returned by forwardRef
is also able to receive a ref
prop.
注意事项
¥Caveats
-
在严格模式下,React 将调用你的渲染函数两次,以便 帮助你发现意外杂质 这是仅开发行为,不会影响生产。如果你的渲染函数是纯函数(它应该是),这应该不会影响你组件的逻辑。其中一个调用的结果将被忽略。
¥In Strict Mode, React will call your render function twice in order to help you find accidental impurities. This is development-only behavior and does not affect production. If your render function is pure (as it should be), this should not affect the logic of your component. The result from one of the calls will be ignored.
render
函数
¥render
function
forwardRef
接受渲染函数作为参数。React 使用 props
和 ref
调用这个函数:
¥forwardRef
accepts a render function as an argument. React calls this function with props
and ref
:
const MyInput = forwardRef(function MyInput(props, ref) {
return (
<label>
{props.label}
<input ref={ref} />
</label>
);
});
参数
¥Parameters
-
props
:父组件传递的属性。¥
props
: The props passed by the parent component. -
ref
:父组件传递的ref
属性。ref
可以是对象或函数。如果父组件没有传递引用,则为null
。你应该将收到的ref
传递给另一个组件,或者将其传递给useImperativeHandle
。¥
ref
: Theref
attribute passed by the parent component. Theref
can be an object or a function. If the parent component has not passed a ref, it will benull
. You should either pass theref
you receive to another component, or pass it touseImperativeHandle
.
返回
¥Returns
forwardRef
返回一个可以在 JSX 中渲染的 React 组件。与定义为普通函数的 React 组件不同,forwardRef
返回的组件能够采用 ref
属性。
¥forwardRef
returns a React component that you can render in JSX. Unlike React components defined as plain functions, the component returned by forwardRef
is able to take a ref
prop.
用法
¥Usage
将 DOM 节点暴露给父组件
¥Exposing a DOM node to the parent component
默认情况下,每个组件的 DOM 节点都是私有的。但是,有时将 DOM 节点公开给父节点很有用 - 例如,允许对其进行聚焦。要选择加入,请将你的组件定义封装到 forwardRef()
中:
¥By default, each component’s DOM nodes are private. However, sometimes it’s useful to expose a DOM node to the parent—for example, to allow focusing it. To opt in, wrap your component definition into forwardRef()
:
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
const { label, ...otherProps } = props;
return (
<label>
{label}
<input {...otherProps} />
</label>
);
});
你将收到 ref 作为属性之后的第二个参数。将它传递给要公开的 DOM 节点:
¥You will receive a ref as the second argument after props. Pass it to the DOM node that you want to expose:
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
const { label, ...otherProps } = props;
return (
<label>
{label}
<input {...otherProps} ref={ref} />
</label>
);
});
这允许父 Form
组件访问 MyInput
公开的 <input>
DOM 节点:
¥This lets the parent Form
component access the <input>
DOM node exposed by 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
组件 传递引用 至 MyInput
。MyInput
组件将该引用转发给 <input>
浏览器标签。结果,Form
组件可以访问 <input>
DOM 节点并在其上调用 focus()
。
¥This Form
component passes a ref to MyInput
. The MyInput
component forwards that ref to the <input>
browser tag. As a result, the Form
component can access that <input>
DOM node and call focus()
on it.
请记住,将引用公开到组件内的 DOM 节点会使以后更改组件的内部结构变得更加困难。你通常会从可重用的底层组件(如按钮或文本输入)公开 DOM 节点,但不会为应用级组件(如头像或评论)公开 DOM 节点。
¥Keep in mind that exposing a ref to the DOM node inside your component makes it harder to change your component’s internals later. You will typically expose DOM nodes from reusable low-level components like buttons or text inputs, but you won’t do it for application-level components like an avatar or a comment.
例子 1 / 2: 聚焦文本输入
¥Focusing a text input
单击按钮将聚焦输入。Form
组件定义了一个引用并将其传递给 MyInput
组件。MyInput
组件将该引用转发给浏览器 <input>
。这让 Form
组件聚焦于 <input>
。
¥Clicking the button will focus the input. The Form
component defines a ref and passes it to the MyInput
component. The MyInput
component forwards that ref to the browser <input>
. This lets the Form
component focus the <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> ); }
通过多个组件转发引用
¥Forwarding a ref through multiple components
除了将 ref
转发到 DOM 节点之外,你还可以将其转发到你自己的组件,例如 MyInput
:
¥Instead of forwarding a ref
to a DOM node, you can forward it to your own component like MyInput
:
const FormField = forwardRef(function FormField(props, ref) {
// ...
return (
<>
<MyInput ref={ref} />
...
</>
);
});
如果该 MyInput
组件将引用转发给它的 <input>
,则对 FormField
的引用将为你提供 <input>
:
¥If that MyInput
component forwards a ref to its <input>
, a ref to FormField
will give you that <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
组件定义了一个引用并将其传递给 FormField
。FormField
组件将该引用转发给 MyInput
,MyInput
将其转发给浏览器 <input>
DOM 节点。这就是 Form
访问该 DOM 节点的方式。
¥The Form
component defines a ref and passes it to FormField
. The FormField
component forwards that ref to MyInput
, which forwards it to a browser <input>
DOM node. This is how Form
accesses that DOM node.
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 节点
¥Exposing an imperative handle instead of a DOM node
无需公开整个 DOM 节点,你可以使用一组更受限制的方法公开一个自定义对象,称为命令式句柄。为此,你需要定义一个单独的引用来保存 DOM 节点:
¥Instead of exposing an entire DOM node, you can expose a custom object, called an imperative handle, with a more constrained set of methods. To do this, you’d need to define a separate ref to hold the DOM node:
const MyInput = forwardRef(function MyInput(props, ref) {
const inputRef = useRef(null);
// ...
return <input {...props} ref={inputRef} />;
});
将收到的 ref
传递给 useImperativeHandle
,并指定要公开给 ref
的值:
¥Pass the ref
you received to useImperativeHandle
and specify the value you want to expose to the 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
的引用,它只会接收你的 { focus, scrollIntoView }
对象而不是 DOM 节点。这使你可以将公开的有关 DOM 节点的信息限制在最低限度。
¥If some component gets a ref to MyInput
, it will only receive your { focus, scrollIntoView }
object instead of the DOM node. This lets you limit the information you expose about your DOM node to the minimum.
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> ); }
¥Read more about using imperative handles.
故障排除
¥Troubleshooting
我的组件封装在 forwardRef
中,但它的 ref
始终是 null
¥My component is wrapped in forwardRef
, but the ref
to it is always null
这通常意味着你忘记实际使用你收到的 ref
。
¥This usually means that you forgot to actually use the ref
that you received.
例如,此组件不对其 ref
执行任何操作:
¥For example, this component doesn’t do anything with its ref
:
const MyInput = forwardRef(function MyInput({ label }, ref) {
return (
<label>
{label}
<input />
</label>
);
});
要修复它,请将 ref
传递给 DOM 节点或另一个可以接受引用的组件:
¥To fix it, pass the ref
down to a DOM node or another component that can accept a ref:
const MyInput = forwardRef(function MyInput({ label }, ref) {
return (
<label>
{label}
<input ref={ref} />
</label>
);
});
如果某些逻辑是有条件的,则 ref
到 MyInput
也可以是 null
:
¥The ref
to MyInput
could also be null
if some of the logic is conditional:
const MyInput = forwardRef(function MyInput({ label, showInput }, ref) {
return (
<label>
{label}
{showInput && <input ref={ref} />}
</label>
);
});
如果 showInput
是 false
,则引用不会被转发到任何节点,并且 MyInput
的引用将保持为空。如果条件隐藏在另一个组件中,例如本例中的 Panel
,这就特别容易被忽略:
¥If showInput
is false
, then the ref won’t be forwarded to any node, and a ref to MyInput
will remain empty. This is particularly easy to miss if the condition is hidden inside another component, like Panel
in this example:
const MyInput = forwardRef(function MyInput({ label, showInput }, ref) {
return (
<label>
{label}
<Panel isExpanded={showInput}>
<input ref={ref} />
</Panel>
</label>
);
});