useImperativeHandle基本使用与实践记录

7,303 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

背景

最近在工作中的项目代码中看见了useImperativeHandle的使用,之前不了解这个api主要用来做什么,也没有再实践中用过它,于是详细了解了一下它的基本用法与使用场景,本文主要记录一下useImperativeHandle的使用场景和如何在项目中利用useImperativeHandle进行实践。

useImperativeHandle语法

useImperativeHandle是React Hooks提供的一个Hook API,表示在使用ref时可以自定义暴露给父组件的实例值,这样可能不太好理解,其实简单解释就是:有一个父组件和子组件,父组件想要直接操作子组件内部的DOM元素或者想要直接使用子组件实例的某些属性或方法时,就可以通过useImperativeHandle这个Hook来实现。

useImperativeHandle(ref, createHandle, [deps])

其中ref为父组件传入的ref值,createHandle是一个函数,返回一个对象,即这个ref的current值,deps为依赖数组,当依赖项发生改变时,会重新执行useImperativeHandle钩子,返回新的对象。

React.forwardRef()转发Ref

但在讲useImperativeHandle如何使用之前,我们再来回顾一下React.forwardRef()透传Ref,也就是转发Ref,它会创建一个React组件,这个组件能够将其接受的ref属性转发到其组件树下的另一个组件中。它常用的场景为:转发ref到DOM组件。

React.forwardRef((props, ref) => (<></>))

其中React.forwardRef 接受一个渲染函数作为参数,该函数接受两个参数,分别是props和ref,props代表父组件传入的属性,ref表示父组件传入的ref。

React.forwardRef()实战

以下为一个简单的示例,定义父组件Father和子组件Child,在子组件Child内部定义了一个input输入框,并传入ref,想实现点击父组件中的按钮能够使子组件内部的输入框自动聚焦,利用React.forward实现代码如下:

import React, { useRef } from "react";

const Child = React.forwardRef((props: any, ref: any) => {
    return (
        <>
        <input ref={ref} />
        {props.children}
        </>
    )
})
const Father = () => {
    const fatherRef = useRef<any>(null);
    const handleClick = () => {
        fatherRef && fatherRef?.current && fatherRef.current.focus();
    }
  return (
    <div style={{ padding: 20 }}>
    <Child ref={fatherRef} />
    <button onClick={handleClick}>点击我操作子组件中的input元素</button>
    </div>
  );
}

当点击按钮后,子组件的输入框会自动聚焦,如下图所示:

image.png 从上述代码可知,通过React.forwardRef()将父组件Father的FatherRef转发到了子组件的input DOM元素上,从而能够通过FatherRef直接操作input元素,使其聚焦。

useImperativeHandle实战

但是React官方说应当避免使用ref这样的命令式代码,所以推荐useImperativeHandle应当与React.forwardRef一起使用。所以将上述代码改为如下形式,能够实现一样的功能:

const Child = React.forwardRef((props: any, ref: any) => {
    const childRef = useRef<any>(null);
    // 设置父组件ref的current
    useImperativeHandle(ref, () => ({
        focus: () => {
            childRef && childRef.current.focus()
        }
    }))
    return (
        <>
        <input ref={childRef} />
        {props.children}
        </>
    )
})
const Father = () => {
    const fatherRef = useRef<any>(null);
    const handleClick = () => {
        // 此处可以直接使用父组件的ref调用子组件暴露出来的focus方法了
        fatherRef.current.focus();
    }
  return (
    <div style={{ padding: 20 }}>
    <Child ref={fatherRef} />
    <button onClick={handleClick}>点击我操作子组件中的input元素</button>
    </div>
  );
}

上述代码利用useImperativeHandle和React.forwardRef将子组件实例的属性或方法暴露给了父组件,所以父组件能够直接操作子组件中的属性或方法,并且子组件的ref和父组件的ref不再使用同一个ref,这也使开发者更加清晰的区分不同组件的ref了。