弃用

在 React 19 中,forwardRef 不再是必需的。将 ref 作为属性传递。

¥In React 19, forwardRef is no longer necessary. Pass ref as a prop instead.

forwardRef 将在未来版本中弃用。了解更多 此处

¥forwardRef will deprecated in a future release. Learn more here.

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) {
// ...
});

请参阅下面的更多示例。

¥See more examples below.

参数

¥Parameters

  • render:组件的渲染函数。React 使用组件从其父组件接收到的属性和 ref 调用此函数。你返回的 JSX 将是组件的输出。

    ¥render: The render function for your component. React calls this function with the props and ref 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 使用 propsref 调用此函数:

¥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: The ref attribute passed by the parent component. The ref can be an object or a function. If the parent component has not passed a ref, it will be null. You should either pass the ref you receive to another component, or pass it to useImperativeHandle.

返回

¥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 转换为 MyInputMyInput 组件将该引用转发给 <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.

Examples of forwarding a ref

例子 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 组件定义一个引用并将其传递给 FormFieldFormField 组件将该引用转发给 MyInputMyInput 再将其转发给浏览器的 <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.

易犯错误

不要过度使用参考文献。你应该只将引用用于你不能表达为属性的命令式行为:例如,滚动到一个节点、聚焦一个节点、触发动画、选择文本等等。

¥Do not overuse refs. You should only use refs for imperative behaviors that you can’t express as props: for example, scrolling to a node, focusing a node, triggering an animation, selecting text, and so on.

如果你可以用属性来表达某些东西,你不应该使用引用。例如,与其从 Modal 组件中暴露 { open, close } 这样的命令式句柄,不如像 <Modal isOpen={isOpen} /> 那样将 isOpen 作为属性。副作用 可以帮助你通过属性暴露命令式行为。

¥If you can express something as a prop, you should not use a ref. For example, instead of exposing an imperative handle like { open, close } from a Modal component, it is better to take isOpen as a prop like <Modal isOpen={isOpen} />. Effects can help you expose imperative behaviors via props.


故障排除

¥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>
);
});

如果某些逻辑是有条件的,则 refMyInput 也可以是 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>
);
});

如果 showInputfalse,则引用不会被转发到任何节点,并且对 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>
);
});