useImperativeHandle

useImperativeHandle 是一个 React Hook,它可以让你自定义作为 ref. 暴露出去的句柄。

useImperativeHandle(ref, createHandle, dependencies?)

参考

useImperativeHandle(ref, createHandle, dependencies?)

在组件顶层调用 useImperativeHandle,以自定义它所暴露的 ref 句柄:

import { useImperativeHandle } from 'react';

function MyInput({ ref }) {
useImperativeHandle(ref, () => {
return {
// ... 你的方法 ...
};
}, []);
// ...

查看更多示例。

参数

  • ref:你作为 props 从 MyInput 组件中接收到的 ref

  • createHandle:一个不接受参数并返回你想要暴露的 ref 句柄的函数。该 ref 句柄可以是任何类型。通常,你会返回一个包含你想要暴露的方法的对象。

  • 可选 dependenciescreateHandle 代码内部引用的所有响应式值列表。响应式值包括 props、state,以及直接在组件函数体内声明的所有变量和函数。如果你的 linter 已为 React 配置,它会验证每个响应式值都被正确地指定为依赖项。依赖项列表必须包含固定数量的项,并且要内联写成 [dep1, dep2, dep3] 这样的形式。React 会使用 Object.is 比较来对比每个依赖项与其之前的值。如果一次重新渲染导致某个依赖项发生变化,或者你省略了这个参数,你的 createHandle 函数将会重新执行,并且新创建的句柄会被赋值给 ref。

Note

从 React 19 开始,ref 可以作为 prop 使用。 在 React 18 及更早版本中,需要通过 forwardRef. 获取 ref

返回值

useImperativeHandle 返回 undefined


用法

向父组件暴露自定义的 ref 句柄

要向父元素暴露一个 DOM 节点,请将 ref prop 传给该节点。

function MyInput({ ref }) {
return <input ref={ref} />;
};

使用上面的代码时,MyInput 的 ref 将接收到 <input> DOM 节点。 不过,你也可以改为暴露一个自定义值。要自定义暴露的句柄,请在组件顶层调用 useImperativeHandle

import { useImperativeHandle } from 'react';

function MyInput({ ref }) {
useImperativeHandle(ref, () => {
return {
// ... 你的方法 ...
};
}, []);

return <input />;
};

注意,在上面的代码中,ref 不再传给 <input>

例如,假设你不想暴露整个 <input> DOM 节点,而是想暴露它的两个方法:focusscrollIntoView。为此,将真实的浏览器 DOM 保存在一个单独的 ref 中。然后使用 useImperativeHandle 来暴露一个只包含你希望父组件调用的方法的句柄:

import { useRef, useImperativeHandle } from 'react';

function MyInput({ ref }) {
const inputRef = useRef(null);

useImperativeHandle(ref, () => {
return {
focus() {
inputRef.current.focus();
},
scrollIntoView() {
inputRef.current.scrollIntoView();
},
};
}, []);

return <input ref={inputRef} />;
};

现在,如果父组件获得了 MyInput 的 ref,它就可以调用其上的 focusscrollIntoView 方法。不过,它不会获得底层 <input> DOM 节点的完整访问权限。

import { useRef } from 'react';
import MyInput from './MyInput.js';

export default function Form() {
  const ref = useRef(null);

  function handleClick() {
    ref.current.focus();
    // 这不会起作用,因为 DOM 节点没有被暴露出来:
    // ref.current.style.opacity = 0.5;
  }

  return (
    <form>
      <MyInput placeholder="Enter your name" ref={ref} />
      <button type="button" onClick={handleClick}>
        Edit
      </button>
    </form>
  );
}


暴露你自己的命令式方法

你通过命令式句柄暴露的方法不必与 DOM 方法完全一致。例如,这个 Post 组件通过命令式句柄暴露了一个 scrollAndFocusAddComment 方法。这让父组件 Page 能够在你点击按钮时滚动评论列表,并聚焦输入框:

import { useRef } from 'react';
import Post from './Post.js';

export default function Page() {
  const postRef = useRef(null);

  function handleClick() {
    postRef.current.scrollAndFocusAddComment();
  }

  return (
    <>
      <button onClick={handleClick}>
        Write a comment
      </button>
      <Post ref={postRef} />
    </>
  );
}

Pitfall

不要过度使用 refs。 你只应该将 refs 用于那些你无法用 props 表达的 命令式 行为:例如,滚动到某个节点、聚焦某个节点、触发动画、选中文本,等等。

如果某件事可以用 prop 表达,就不应该使用 ref。 例如,与其从 Modal 组件中暴露像 { open, close } 这样的命令式句柄,不如将 isOpen 作为 prop 传入,例如 <Modal isOpen={isOpen} />Effects 可以帮助你通过 props 暴露命令式行为。