当组件是通用的(多态反应组件)时键入点击处理程序

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 实用程序