ref + forwardRef + useImperativeHandle

156 阅读2分钟

前言

有时候我们需要通过 ref 获取某个组件的 ref,并使用 ref 提供的功能,组件化是,我们还可能需要自定义组件,并要给外部 ref 供外部使用,因此也会用到 forwardRef + useImperativeHandle 相关功能

这篇文章介绍 hook 的下 ref 的获取,以及自定义组件并暴露 ref 相应功能的内容

ref

平时我们可以通过 ref 获取某个组件的 ref,并使用 ref 提供的功能,很简单的功能,如果我们要设置

const Index = () => {
  const ref = React.useRef(null)

  return (
    <div ref={ ref }>
   </div>
  )
}

后面我们发现,ref 不只是 dom 类型,它还可以是很多自定义类型,比如经常使用的 antd

我们封装组件时,有时希望双方能够更加友好交互,仅仅是一个 setState 是不合适的,因此有时会对外暴露一个接口,方便外部调用我们内部提供的方法(避免不必要的 setState),这个接口就是我们的 ref

forwardRef + useImperativeHandle

下面提供一个案例,我们使用自定义 hook 自定义一个组件,有时候需要外面主动刷新一下(暂定一个刷新功能),因此需要用到 forwardRef 转发功能,由于函数组件没有实例,需要使用 useImperativeHandle 接收对象(看名字也知道必要的把手,句柄)

//我们自定义的类型,外部可以通过这个类型来更好使用我们的ref
//对于 js 则不用声明类型了,直接使用,对于使用组件来说,自然是 ts 的组件使用体验更友好
export type TableProRef = {
    reload: Function,
    ...
};

//封装一个函数组件,forwardRef<TableProRef, any>(props, ref)
//forwardRef 支持两个参数 props、ref,而参数的类型泛型顺序是颠倒的,ref、props,因此需要注意,不过点进去看看就不会出出错了
const TableProComponent = forwardRef<TableProRef, any>(props, ref) => {
    //前面声明好了泛型,下面就根据type类型返回我们的ref句柄,以提供外部调用的功能
    useImperativeHandle(ref, () => ({
        reload,
        ...
    }));
    
    const reload = () => onSearch();
    ...
}

//需要注意的是,有提示要加上 displayName,不然会有警告,这里也加上了,调试时,可能更容易看到hook 组件名称
TableProComponent.displayName = 'CustomTableProComponent';

这样外部就可以直接通过 useRef 获取到的 ref 直接调用里面提供的方法了

const ref = useRef<TableProRef>()

ref.current.reload()

<TableProComponent ref={ref} ... >

二次封装转发ref

如果有二次封装,并需要转发内部组件的 ref,那么直接将要抓发的 ref 传递给内部组件就行了,就不多说了哈

const TableProComponent1 = forwardRef<ActionType, any>((props, ref) => {
    //ProTable 的 actionRef 的类型是 ActionType 上面标记即可
    return <ProTable actionRef={ref} />;
});