Это компонент, над которым я сейчас работаю, названный 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>
);
};
Можно ли расширить HTMLAttributes<HTMLSpanElement>
, если span
prop передан, и только 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>>;
Если у кого-то есть какие-либо дополнительные улучшения, которые я мог бы использовать, пожалуйста, помогите, спасибо 😄