弃用

在 React 19 中,不再需要 forwardRef。请改为将 ref 作为 prop 传递。

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

forwardRef 将在未来的版本中被弃用。了解更多信息请点击 这里

forwardRef 让你的组件通过 ref. 向父组件暴露一个 DOM 节点

const SomeComponent = forwardRef(render)

参考

🌐 Reference

forwardRef(render)

调用 forwardRef() 以让你的组件接收一个 ref 并将其转发给子组件:

🌐 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 会使用你的组件从其父组件接收到的 props 和 ref 来调用这个函数。你返回的 JSX 将成为组件的输出。

返回

🌐 Returns

forwardRef 返回一个可以在 JSX 中渲染的 React 组件。与作为普通函数定义的 React 组件不同,forwardRef 返回的组件还能够接收一个 ref 属性。

注意事项

🌐 Caveats

  • 在严格模式下,React 会调用你的渲染函数两次,以便帮助你发现意外的副作用。这是仅在开发环境中的行为,对生产环境没有影响。如果你的渲染函数是纯粹的(就像它应该的那样),这不会影响组件的逻辑。两次调用中的一个结果将被忽略。

render 函数

🌐 render function

forwardRef 接受一个渲染函数作为参数。React 会用 propsref 调用这个函数:

const MyInput = forwardRef(function MyInput(props, ref) {
return (
<label>
{props.label}
<input ref={ref} />
</label>
);
});

参数

🌐 Parameters

  • props:父组件传递的属性。
  • ref:父组件传递的 ref 属性。ref 可以是一个对象或一个函数。如果父组件没有传递 ref,它将是 null。你应该将你收到的 ref 传递给另一个组件,或者传递给 useImperativeHandle.

返回

🌐 Returns

forwardRef 返回一个可以在 JSX 中渲染的 React 组件。与作为普通函数定义的 React 组件不同,forwardRef 返回的组件能够接收一个 ref 属性。


用法

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

你将在 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 访问由 MyInput 暴露的 <CodeStep step={2}> <input> DOM 节点 </CodeStep>

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 组件 传递一个 refMyInputMyInput 组件 转发 该 ref 到 <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 of 2:
聚焦文本输入

🌐 Focusing a text input

点击按钮将聚焦输入框。Form 组件定义了一个 ref 并将其传递给 MyInput 组件。MyInput 组件将该 ref 转发给浏览器 <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 转发给你自己的组件,如 MyInput,而不是转发给 DOM 节点:

🌐 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 组件将 ref 转发给它的 <input>,那么对 FormField 的 ref 将给你那个 <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 组件定义了一个 ref 并将其传递给 FormFieldFormField 组件将该 ref 转发给 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 节点,你可以公开一个自定义对象,称为命令句柄,它包含一组更受限制的方法。为此,你需要定义一个单独的 ref 来保存 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>
  );
}

阅读更多关于使用命令句柄的信息。

易犯错误

不要过度使用 refs。 你应当仅在无法通过 props 表达的强制性行为上使用 refs:例如,滚动到某个节点、聚焦某个节点、触发动画、选择文本等等。

如果你可以将某个东西作为 prop 来表达,就不应该使用 ref。 例如,与其从 Modal 组件中暴露像 { open, close } 这样的命令式句柄,不如将 isOpen 作为 prop 传入 <Modal isOpen={isOpen} />Effects 可以帮助你通过 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 传递给可以接受 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,那么 ref 不会被转发到任何节点,并且对 MyInput 的 ref 将保持为空。如果条件隐藏在另一个组件中,例如此示例中的 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>
);
});