简体   繁体   English

多态组件 - 使用 Typescript 从 React 中的道具中选择类型

[英]Polymorphic component - selecting type from a prop in React with Typescript

This is a component I'm currently working on, named TextBody这是我目前正在处理的一个组件,名为TextBody

import { HTMLAttributes } from "react";
import classNames from "classnames";

interface TextBodyProps
  extends HTMLAttributes<HTMLParagraphElement | HTMLSpanElement> {
  span?: boolean;
  type: "s" | "m";
}

export const TextBody = ({
  span,
  type,
  className,
  children,
  ...props
}: TextBodyProps) => {
  const textBodyClassNames = {
    s: "text-body-s font-light leading-relaxed max-w-sm",
    m: "text-body-m font-light leading-relaxed max-w-sm",
  };

  const TextBodyElement = span ? "span" : "p";

  return (
    <TextBodyElement
      {...props}
      className={classNames(textBodyClassNames[type], className)}
    >
      {children}
    </TextBodyElement>
  );
};

Is it possible to extend HTMLAttributes<HTMLSpanElement> if span prop is passed, and only HTMLAttributes<HTMLParagraphElement> if it's not, instead of having a union?如果传递了span属性,是否可以扩展HTMLAttributes<HTMLSpanElement> ,如果不是,则只扩展 HTMLAttributes HTMLAttributes<HTMLParagraphElement> ,而不是联合?

This is too long for a comment, and may be an answer.这对于评论来说太长了,可能是一个答案。 I would code it instead as follows:我会将其编码如下:

import { HTMLAttributes } from "react";
import classNames from "classnames";

// This is a more accurate union: you have *either* the span
// attributes *or* the paragraph attributes, not a union of the
// attributes of both.
type TextBodyProps = (HTMLAttributes<HTMLParagraphElement> | HTMLAttributes<HTMLSpanElement>) & {
  span?: boolean;
  type: "s" | "m";
};

export const TextBody = ({
  span,
  type,
  className,
  children,
  ...props
}: TextBodyProps) => {
  return span ? (
    <span
      {...props}
      className={classNames("text-body-s font-light leading-relaxed max-w-sm", className)}
    >
      {children}
    </span>
  ) : (
    <p
      {...props}
      className={classNames("text-body-m font-light leading-relaxed max-w-sm", className)}
    >
      {children}
    </p>
  );
};

Is there a little bit of duplication?有一点重复吗? Yes.是的。 But a little duplication is better than premature abstraction , YAGNI , etc. That one in the original example was awkward to implement.但是一点点重复总比过早抽象YAGNI等要好。原始示例中的那个很难实现。 Maybe there's an elegant way to have your cake and eat it too here, but I'd start with the simple easy-to-implement easy-to-read version first.也许这里有一种既能吃又能吃蛋糕的优雅方式,但我会首先从简单、易于实现、易于阅读的版本开始。

This is the final solution I've come up with:这是我想出的最终解决方案:

import { ElementType } from "react";
import classNames from "classnames";
import { PolymorphicComponentProps } from "../../types";

type Variants = {
  s: string;
  m: string;
};

type TextBodyProps = {
  type: keyof Variants;
};

const textBodyClassNames: Variants = {
  s: "text-body-s font-light leading-relaxed",
  m: "text-body-m font-light leading-relaxed",
};

const defaultComponent = "span";

export const TextBody = <
  Component extends ElementType = typeof defaultComponent
>({
  as,
  type,
  className,
  children,
  ...props
}: PolymorphicComponentProps<Component, TextBodyProps>) => {
  const Component = as || defaultComponent;

  return (
    <Component
      {...props}
      className={classNames(textBodyClassNames[type], className)}
    >
      {children}
    </Component>
  );
};

I think it makes it very clear how to expand it by adding a new variant, changing a variant style, or changing the default component if not specified.我认为它非常清楚如何通过添加新变体、更改变体样式或更改默认组件(如果未指定)来扩展它。

PolymorphicComponentProps file contains: PolymorphicComponentProps 文件包含:

import {
  ComponentPropsWithoutRef,
  PropsWithChildren,
  ElementType,
} from "react";

type AsProp<Component extends ElementType> = {
  as?: Component;
};

type PropsToOmit<
  Component extends ElementType,
  Props
> = keyof (AsProp<Component> & Props);

export type PolymorphicComponentProps<
  Component extends ElementType,
  Props = {}
> = PropsWithChildren<Props & AsProp<Component>> &
  Omit<ComponentPropsWithoutRef<Component>, PropsToOmit<Component, Props>>;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM