@Mashush

Как передать в React.forwardRef компонент дженерик в виде названия элемента?

Введение:

Необходимо написать компонент Buttonс использованием React.forwardRef, но с возможностью выбора отображаемого элемента, как это реализуется в некоторых библиотеках. Например, по умолчанию отображается элемент button, но если я передаю параметр href, то должна отображаться ссылка. Важный момент заключается в том, что мне нужно связать это поведение с ref - по умолчанию ref должен принимать ссылку типа React.Ref<HTMLButtonElement>, но если я передаю через props параметр href, то ref должен принимать ссылку типа React.Ref<HTMLAnchorElement>

Попытки:

Пока не сильно разбираюсь в тонкостях языка, но вот что у меня получилось накидать.

Жалкие попытки...
// общие параметры
type CommonProps = {
  children: React.ReactNode
  variant?: 'plain' | 'tonal' | 'filled' | 'outlined'
  justify?: 'start' | 'center' | 'end'
}

type Props<T extends 'a' | 'button' | never> = (
  T extends 'button' | 'a'
    ? T extends 'a'
      ? CommonProps & { href: string } & React.ComponentPropsWithoutRef<T>
      : CommonProps & { href?: never } & React.ComponentPropsWithoutRef<T>
    : CommonProps & { href?: never } & React.ComponentPropsWithoutRef<'button'>
)

const Button = React.forwardRef(<T extends 'a' | 'button' | never>(props: Props<T>, ref: React.Ref<React.ComponentRef<T>>) => {

  if (typeof props.href === 'string') {
    return (
      <a className="button" ref={ref as React.Ref<React.ComponentRef<'a'>>}>
        {props.children}
      </a>
    )
  }

  return (
    <button className="button" ref={ref as React.Ref<React.ComponentRef<'button'>>}>
      {props.children}
    </button>
  )

})

export default Button


Текущий результат:

На данный момент я чувствую что иду в нужном направлении, но все никак не могу понять что конкретно делаю не так, уже мозг вскипает, если честно. Повторюсь что конкретно я ожидаю:

Вызов <Button /> должен ожидать атрибуты только для кнопки (ButtonProps);
Вызов <Button href="/" /> должен ожидать атрибуты только для ссылки (AnchorProps);
Вызов <Button ref={buttonRef} /> должен ожидать атрибуты только для кнопки (ButtonProps);
Вызов <Button ref={anchorRef} /> должен ожидать атрибуты только для ссылки (AnchorProps).

Надеюсь понятно объяснил, заранее благодарю)
  • Вопрос задан
  • 76 просмотров
Пригласить эксперта
Ответы на вопрос 1
WblCHA
@WblCHA
type ButtonType = 'button' | 'a';

interface ButtonTypeProps extends Record<ButtonType, object> {
  button: {};
  a: { href: string };
}

type CommonProps = {
  children: React.ReactNode;
  variant?: 'plain' | 'tonal' | 'filled' | 'outlined';
  justify?: 'start' | 'center' | 'end';
};

type Props<T extends ButtonType> = ButtonTypeProps[T] & CommonProps & ComponentPropsWithoutRef<T>;

type ButtonComponent<T extends ButtonType = ButtonType> = UnionToIntersection<
  T extends T ? ForwardRefExoticComponent<Props<T> & RefAttributes<ComponentRef<T>>> : never
>;

const isLinkProps = (props: Props<ButtonType>): props is Props<'a'> => 'href' in props;

const Button = forwardRef(
  <T extends ButtonType = ButtonType>(props: Props<T>, ref: ForwardedRef<ComponentRef<T>>) => {
    if (isLinkProps(props)) {
      return (
        <a ref={ref as ForwardedRef<ComponentRef<'a'>>} href={props.href} className="button">
          {props.children}
        </a>
      );
    }

    return (
      <button ref={ref as ForwardedRef<ComponentRef<'button'>>} type="button" className="button">
        {props.children}
      </button>
    );
  },
) as ButtonComponent;

Button.displayName = 'Button';


https://www.typescriptlang.org/play?#code/JYWwDg9g...
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы