当组件是通用的(多态反应组件)时键入点击处理程序
Typing the click handler when component is generic (polymorphic react component)
这是我的组件
interface Props<C extends React.ElementType> {
as?: C;
children: React.ReactNode;
size?: "mini" | "small" | "medium" | "large";
onClick?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
}
type ButtonProps<C extends React.ElementType> = Props<C> &
Omit<React.ComponentPropsWithRef<C>, keyof Props<C>>;
export const Button = <C extends React.ElementType = "button">({
children,
as,
size = "medium",
onClick,
...restProps
}: ButtonProps<C>) => {
const Component = as || "button";
return (
<Component {...restProps} className={`button-${size}`} onClick={onClick}>
{children}
</Component>
);
};
我可以这样使用组件:
<Button as="a" onClick={(e) => console.log(e)}>Anchor button</a>
问题是点击处理程序的类型表明事件是 React.MouseEvent<HTMLButtonElement, MouseEvent>
类型,但事实并非如此。在这种情况下应该是HTMLAnchorElement
。
如何提供正确的类型?理想情况下,我会以某种方式使用 C
来获得正确的 HTMLXyzElement
这里有一个 TypeScript Playground link 如果您愿意,您可以在其中试用代码
您可以使用 HTMLElement
而不是 HTMLButtonElement
import React, { useRef } from "react";
type InferElement<T> =
T extends keyof JSX.IntrinsicElements
? JSX.IntrinsicElements[T] extends React.DetailedHTMLProps<React.AnchorHTMLAttributes<any>, infer Elem>
? Elem
: never
: HTMLElement
type Result = InferElement<'a'> // HTMLAnchorElement
interface Props<C extends React.ElementType> {
as?: C;
children: React.ReactNode;
size?: "mini" | "small" | "medium" | "large";
onClick?: (event: React.MouseEvent<InferElement<C>, MouseEvent>) => void;
}
type ButtonProps<C extends React.ElementType> = Props<C> &
Omit<React.ComponentPropsWithRef<C>, keyof Props<C>>;
export const Button = <C extends React.ElementType = "button">({
children,
as,
size = "medium",
onClick,
...restProps
}: ButtonProps<C>) => {
const Component: React.ElementType = as || "button";
return (
<Component {...restProps} className={`button-${size}`} onClick={onClick}>
{children}
</Component>
);
};
const Link = ({ to, children }: { to: string; children: React.ReactNode }) => {
return <a href={to}>{children}</a>;
};
export const ButtonUser = () => {
return (
<div>
<Button size="mini" disabled>
Button text
</Button>
<Button as="a" size="mini" href="test" onClick={(e) => console.log(e)}>
Button text
</Button>
<Button as={Link} to="/" size="mini">
Button text
</Button>
</div>
);
};
const jsx = <Button as="a" onClick={(e) => console.log(e)}>Anchor button</Button>
由于 JSX.IntrinsicElements
只是 html 元素的大映射,您可以从 JSX.IntrinsicElements
推断出 html 元素名称并将其用于回调参数。参见 InferElement
实用程序
这是我的组件
interface Props<C extends React.ElementType> {
as?: C;
children: React.ReactNode;
size?: "mini" | "small" | "medium" | "large";
onClick?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
}
type ButtonProps<C extends React.ElementType> = Props<C> &
Omit<React.ComponentPropsWithRef<C>, keyof Props<C>>;
export const Button = <C extends React.ElementType = "button">({
children,
as,
size = "medium",
onClick,
...restProps
}: ButtonProps<C>) => {
const Component = as || "button";
return (
<Component {...restProps} className={`button-${size}`} onClick={onClick}>
{children}
</Component>
);
};
我可以这样使用组件:
<Button as="a" onClick={(e) => console.log(e)}>Anchor button</a>
问题是点击处理程序的类型表明事件是 React.MouseEvent<HTMLButtonElement, MouseEvent>
类型,但事实并非如此。在这种情况下应该是HTMLAnchorElement
。
如何提供正确的类型?理想情况下,我会以某种方式使用 C
来获得正确的 HTMLXyzElement
这里有一个 TypeScript Playground link 如果您愿意,您可以在其中试用代码
您可以使用 HTMLElement
而不是 HTMLButtonElement
import React, { useRef } from "react";
type InferElement<T> =
T extends keyof JSX.IntrinsicElements
? JSX.IntrinsicElements[T] extends React.DetailedHTMLProps<React.AnchorHTMLAttributes<any>, infer Elem>
? Elem
: never
: HTMLElement
type Result = InferElement<'a'> // HTMLAnchorElement
interface Props<C extends React.ElementType> {
as?: C;
children: React.ReactNode;
size?: "mini" | "small" | "medium" | "large";
onClick?: (event: React.MouseEvent<InferElement<C>, MouseEvent>) => void;
}
type ButtonProps<C extends React.ElementType> = Props<C> &
Omit<React.ComponentPropsWithRef<C>, keyof Props<C>>;
export const Button = <C extends React.ElementType = "button">({
children,
as,
size = "medium",
onClick,
...restProps
}: ButtonProps<C>) => {
const Component: React.ElementType = as || "button";
return (
<Component {...restProps} className={`button-${size}`} onClick={onClick}>
{children}
</Component>
);
};
const Link = ({ to, children }: { to: string; children: React.ReactNode }) => {
return <a href={to}>{children}</a>;
};
export const ButtonUser = () => {
return (
<div>
<Button size="mini" disabled>
Button text
</Button>
<Button as="a" size="mini" href="test" onClick={(e) => console.log(e)}>
Button text
</Button>
<Button as={Link} to="/" size="mini">
Button text
</Button>
</div>
);
};
const jsx = <Button as="a" onClick={(e) => console.log(e)}>Anchor button</Button>
由于 JSX.IntrinsicElements
只是 html 元素的大映射,您可以从 JSX.IntrinsicElements
推断出 html 元素名称并将其用于回调参数。参见 InferElement
实用程序