React.forwardRef参数的TypeScript定义
前言
在React中,forwardRef
和useImperativeHandle
钩子常被用于函数组件,以暴露组件内部的方法给父组件。通过明确地定义和使用类型,我们可以提高组件间的交互质量,确保类型安全,并利用自动补全等IDE特性。以下借用FancyButton
组件暴露focus
方法的实现过程来介绍是如何进行类型定义的。
定义组件Props和Ref接口
首先,定义组件的props和ref接口,这有助于在组件外部通过ref对象调用组件内部的方法时获得类型检查和自动补全的好处。
import React, { useImperativeHandle, createRef } from 'react';
export interface FancyButtonRef {
focus: () => void;
}
interface FancyButtonProps {
label: string;
onClick?: () => void;
}
实现FancyButton
组件
在FancyButton
组件中,我们使用React.forwardRef
和useImperativeHandle
来暴露focus
方法。
import React, { useImperativeHandle, createRef } from 'react';
export interface FancyButtonRef {
focus: () => void;
}
interface FancyButtonProps {
label: string;
onClick?: () => void;
}
const FancyButton = React.forwardRef<FancyButtonRef, FancyButtonProps>(
(props, ref) => {
const { label, onClick } = props;
const buttonRef = createRef<HTMLButtonElement>();
useImperativeHandle(ref, () => ({
focus: () => {
buttonRef.current?.focus();
},
}));
return (
<button ref={buttonRef} onClick={onClick}>
{label}
</button>
);
}
);
export default FancyButton;
在父组件中使用FancyButton
父组件通过ref访问FancyButton
的focus
方法,在以下代码中展示了如何在实际场景中使用这种方法。
import { useRef } from 'react';
import FancyButton from './FancyButton';
import type { FancyButtonRef } from './FancyButton';
const ParentComponent: FC = () => {
const fancyButtonRef = useRef<FancyButtonRef>(null);
const onButtonClick = () => {
fancyButtonRef.current?.focus();
};
return (
<div>
<FancyButton ref={fancyButtonRef} label="点击我" />
<button onClick={onButtonClick}>使旁边这个按钮聚焦</button>
</div>
);
};
export default ParentComponent;
在FancyButton
组件的实现代码中,将FancyButtonRef
和FancyButtonProps
作为forwardRef
的泛型参数引入,这种做法会导致在父组件中通过ref对象调用FancyButton
内部方法时,提供了类型检查和自动补全功能,从而增强了代码的健壮性和开发效率。
在forwardRef
包裹组件的子组件使用useImperativeHandle
import React, { useImperativeHandle, forwardRef, createRef } from 'react';
interface FancyButtonProps {
label: string;
onClick?: () => void;
}
export interface FancyButtonRef {
focus: () => void;
}
const InnierFancyButton: React.FC<any> = ({ ref, label, onClick }) => {
const buttonRef = createRef<HTMLButtonElement>();
useImperativeHandle(ref, () => ({
focus: () => {
buttonRef.current?.focus();
},
}));
return (
<button ref={buttonRef} onClick={onClick}>
{label}
</button>
);
};
const FancyButton = forwardRef<FancyButtonRef, FancyButtonProps>(
(props, ref) => {
return (
<InnierFancyButton {...props} ref={ref} />
);
}
);
export default FancyButton;
在实际开发中,并不会在forwardRef
包裹组件中直接使用useImperativeHandle
,也许会在组件的子组件中使用useImperativeHandle
。
比如在 InnierFancyButton
组件中使用 useImperativeHandle
,那么InnierFancyButton
子组件的props类型如何定义?
这个很好定义,使用 extends
继承 FancyButtonProps
类型,再使用React.ForwardedRef<FancyButtonRef>
定义 ref
的类型。
import React, { useImperativeHandle, forwardRef, createRef } from 'react';
interface FancyButtonProps {
label: string;
onClick?: () => void;
}
export interface FancyButtonRef {
focus: () => void;
}
+ interface InnierFancyButtonProps extends FancyButtonProps {
+ ref: React.ForwardedRef<FancyButtonRef>
+ }
- const InnierFancyButton: React.FC<any> = ({ ref, label, onClick }) => {
+ const InnierFancyButton: React.FC<InnierFancyButtonProps> = ({ ref, label, onClick }) => {
const buttonRef = createRef<HTMLButtonElement>();
useImperativeHandle(ref, () => ({
focus: () => {
buttonRef.current?.focus();
},
}));
return (
<button ref={buttonRef} onClick={onClick}>
{label}
</button>
);
};
const FancyButton = forwardRef<FancyButtonRef, FancyButtonProps>(
(props, ref) => {
return (
<InnierFancyButton {...props} ref={ref} />
);
}
);
export default FancyButton;
使用 ComponentPropsWithoutRef
优化类型定义
InnierFancyButtonProps
类型定义还可以优化一下,其实 FancyButtonProps
这个类型有点多余,可以使用 ComponentPropsWithoutRef
这个泛型类型优化掉。
import React, { useImperativeHandle, forwardRef, createRef } from 'react';
- interface FancyButtonProps {
- label: string;
- onClick?: () => void;
- }
export interface FancyButtonRef {
focus: () => void;
}
- interface InnierFancyButtonProps extends FancyButtonProps {
+ interface InnierFancyButtonProps {
ref: React.ForwardedRef<FancyButtonRef>
+ label: string;
+ onClick?: () => void;
}
const InnierFancyButton: React.FC<InnierFancyButtonProps> = ({ ref, label, onClick }) => {
const buttonRef = createRef<HTMLButtonElement>();
useImperativeHandle(ref, () => ({
focus: () => {
buttonRef.current?.focus();
},
}));
return (
<button ref={buttonRef} onClick={onClick}>
{label}
</button>
);
};
- const FancyButton = forwardRef<FancyButtonRef, FancyButtonProps>(
+ const FancyButton = forwardRef<FancyButtonRef, ComponentPropsWithoutRef<typeof InnierFancyButton>>(
(props, ref) => {
return (
<InnierFancyButton {...props} ref={ref} />
);
}
);
export default FancyButton;
先使用 typeof
在 InnierFancyButton
组件,会获得了一个表示该组件类型的对象。这包括了它的 props 类型以及它可能返回的元素类型。然后使用 ComponentPropsWithoutRef
从组件类型中提取 props 类型,但不包括 ref 属性。
转载自:https://juejin.cn/post/7331544684290015258