forwardRef

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 函数 forwardRef 接收一个渲染函数作为参数。React 使用 propsref 来调用此函数

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

参数

  • props:父组件传递的 props。

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

返回值

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


用法

将 DOM 节点暴露给父组件

默认情况下,每个组件的 DOM 节点都是私有的。但是,有时将 DOM 节点暴露给父组件很有用,例如,允许对其进行聚焦。要选择加入,请将您的组件定义包装到 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 暴露的 <input> DOM 节点

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()

请记住,将 ref 暴露给组件内部的 DOM 节点会使得以后更难更改组件的内部结构。您通常会从可重用的低级组件(如按钮或文本输入)中公开 DOM 节点,但不会对应用程序级组件(如头像或评论)执行此操作。

转发 ref 的示例

聚焦文本输入的示例 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 并将其传递给 FormFieldFormField 组件将该 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>
  );
}

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

陷阱

不要过度使用 ref。 您应该只将 ref 用于您无法以 props 形式表达的_命令式_行为:例如,滚动到节点、聚焦节点、触发动画、选择文本等等。

如果您可以将某些内容表示为 prop,则不应使用 ref。 例如,与其从 Modal 组件公开命令式句柄(如 { open, close }),不如将 isOpen 作为 prop(如 <Modal isOpen={isOpen} />)来使用。副作用可以帮助您通过 props 公开命令式行为。


故障排除

我的组件包装在 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>
);
});

如果某些逻辑是有条件的,则对 MyInputref 也可能为 null

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

如果 showInputfalse,则 ref 不会被转发到任何节点,并且对 MyInput 的 ref 将保持为空。如果条件隐藏在另一个组件(例如本例中的 Panel)中,则此问题尤其容易被忽略

const MyInput = forwardRef(function MyInput({ label, showInput }, ref) {
return (
<label>
{label}
<Panel isExpanded={showInput}>
<input ref={ref} />
</Panel>
</label>
);
});