Table of Contents

1、概述

useImperativeHandle 是 React 中的一个相对高级且强大的 Hook. 当我们想要在父组件中,调用自定义子组件内部的方法时,就需要使用到这个 Hook.

index.tsx
1
useImperativeHandle(ref, createHandle, deps?)

通常,我们会通过 ref 来获取子组件的引用,例如,默认的 input 组件,我们可以通过 ref 来获取其引用,然后调用其方法

预览
index.tsx
01
import { useRef } from "react"
02
03
export default function Demo01() {
04
const inputRef = useRef<HTMLInputElement>(null);
05
06
function __clickHandler() {
07
inputRef.current?.focus();
08
}
09
10
return (
11
<div className="flex gap-4 p-4">
12
<input ref={inputRef} className="grow" />
13
<button onClick={__clickHandler} className="button">点击获取焦点</button>
14
</div>
15
);
16
}

但是许多时候, 直接使用 input 标签并不能满足我们的需求, 我们需要基于 input 做额外的封装. 但是封装之后, 我们还是希望能通过调用 .focus 让输入框获取焦点.

index.tsx
1
const input = useRef(null)
2
...
3
<Input ref={input} type='text' />
4
...
5
input.current.focus()

我们使用封装之后的 Input, 无法直接拿到内部的 input 对象, 但是我们的目标依然是获取 input 对象, 然后调用 focus,这里就需要借助 forwardRefuseImperativeHandle 来实现

2、forwardRef 基础

WARNING

在 React 19 中,我们不再使用 forwardRef 来实现 ref 的传递,请参考 React 19 中 ref 机制更改, forwardRef 被无情抛弃

在之前的版本中,forwardRef 能够帮助 React 组件传递 ref. 或者说是内部对象控制权的转移与转发. 它接收一个组件作为参数,然后返回一个可渲染的函数组件

forwardref.tsx
1
// 注意 ref 的传递方式
2
function MyInput(props, ref) {
3
return (
4
<input ref={ref} {...props} />
5
)
6
}
7
8
const Input = forwardRef(MyInput)

自定义 Input 组件的使用与代码如下所示

预览
index.tsx
Input.tsx
01
import { useRef } from "react"
02
import Input from "./Input"
03
export default function Demo01() {
04
const inputRef = useRef<HTMLInputElement>(null);
05
06
function __clickHandler() {
07
inputRef.current?.focus();
08
}
09
10
return (
11
<div className="flex gap-4 p-4">
12
<Input ref={inputRef} className="grow" />
13
<button onClick={__clickHandler} className="button">点击获取焦点</button>
14
</div>
15
);
16
}

3、使用 useImperativeHandle 在子组件内部自定义方法

当我们想要自定义子组件的内部方法时,除了需要借助 forwardRef 来传递 ref 之外,还需要借助 useImperativeHandle 来在子组件内部自定义方法

index.tsx
1
useImperativeHandle(ref, createHandle, deps?)
  1. ref 是父组件传递进来的 ref 引用
  2. createHandle 是一个回调函数,需要返回一个对象,这个对象就是子组件内部的方法
  3. deps 是依赖项,当依赖项发生变化时,会重新调用 createHandle 函数

在下面的案例中,我们使用 useImperativeHandle 在子组件内部定义了弹窗的打开与关闭方法,并将其暴露给父组件,父组件可以通过 ref 来调用这些方法

预览
index.tsx
modal.tsx
01
import { useRef } from 'react';
02
import Modal, { ElegantModalHandle } from './modal';
03
04
export default function App() {
05
const modalRef = useRef<ElegantModalHandle>(null);
06
07
return (
08
<div className="flex items-center justify-center p-8 gap-4">
09
<button onClick={() => modalRef.current?.open()} className="button">
10
打开弹窗
11
</button>
12
<Modal ref={modalRef} />
13
</div>
14
);
15
}

4、验证输入框控制焦点与抖动反馈

在下面的案例中,我们实现了输入框的振动反馈。没有新的知识点,仅用于扩展实践场景,大家直接看代码学习

预览
index.tsx
input.tsx
01
import { useRef } from 'react';
02
import CustomInput, { CustomInputHandle } from './input';
03
04
export default function FormPage() {
05
const inputRef = useRef<CustomInputHandle>(null);
06
07
return (
08
<div className="flex flex-col items-center justify-center px-4 py-8">
09
<div className="w-full max-w-sm space-y-4">
10
<CustomInput ref={inputRef} />
11
<div className="flex gap-4 justify-end">
12
<button onClick={() => inputRef.current?.focus()} className="button">
13
获取焦点
14
</button>
15
<button onClick={() => inputRef.current?.shake()} className="button border-red-500 text-red-500">
16
触发错误抖动
17
</button>
18
</div>
19
</div>
20
</div>
21
);
22
}

4、总结

useImperativeHandle 在封装组件时,可以让我们在子组件内部定义方法,并将其暴露给父组件,父组件可以通过 ref 来调用这些方法,从而非常方便的控制子组件的行为。这样的特性对自定义组件非常有用,在 React 19 中,对于 forwardRef 的使用有一些改动,我在React 19 中 ref 机制更改, forwardRef 被无情抛弃中已经进行了详细的介绍,你也可以进一步学习

专栏首页
到顶
专栏目录