簡體   English   中英

多態組件 - 使用 Typescript 從 React 中的道具中選擇類型

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

這是我目前正在處理的一個組件,名為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>
  );
};

如果傳遞了span屬性,是否可以擴展HTMLAttributes<HTMLSpanElement> ,如果不是,則只擴展 HTMLAttributes HTMLAttributes<HTMLParagraphElement> ,而不是聯合?

這對於評論來說太長了,可能是一個答案。 我會將其編碼如下:

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>
  );
};

有一點重復嗎? 是的。 但是一點點重復總比過早抽象YAGNI等要好。原始示例中的那個很難實現。 也許這里有一種既能吃又能吃蛋糕的優雅方式,但我會首先從簡單、易於實現、易於閱讀的版本開始。

這是我想出的最終解決方案:

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>
  );
};

我認為它非常清楚如何通過添加新變體、更改變體樣式或更改默認組件(如果未指定)來擴展它。

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