Полиморфный компонент — выбор типа из реквизита в React с помощью 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>
  );
};

Можно ли расширить HTMLAttributes<HTMLSpanElement>, если span prop передан, и только HTMLAttributes<HTMLParagraphElement>, если нет, вместо союза?

Если у кого-то есть какие-либо дополнительные улучшения, которые я мог бы использовать, пожалуйста, помогите, спасибо 😄

Ilija Ivic 23.11.2022 20:01
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
1
69
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Это слишком долго для комментария и может быть ответом. Вместо этого я бы закодировал его следующим образом:

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

Другие вопросы по теме