forwardRef
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
prop。
¥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
可以是对象或函数。如果父组件未传递 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
prop。
¥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
组件将 传递一个 ref 转换为 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 节点,但对于头像或评论等应用级组件,你不会这样做。
¥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>
);
});