michael_mashush
@michael_mashush

Как перегрузить React.ForwardRefRenderFunction компонент?

Есть следующий компонент кнопки, который я постарался максимально упростить для примера:

import React    from 'react'
import { Link } from 'react-router-dom'

type ButtonProps = (
  & React.ComponentPropsWithoutRef<'button'>
  & React.AriaAttributes
  & { href?: never }
)

type AnchorProps = (
  & Omit<LinkProps, 'to'>
  & React.AriaAttributes
  & { href: string }
)

type OverloadedComponent = (
  & React.FunctionComponent<ButtonProps>
  & React.FunctionComponent<AnchorProps>
)

const isAnchor = (props: ButtonProps | AnchorProps): props is AnchorProps => 'href' in props

const Button: OverloadedComponent = (props) => {
  if (isAnchor(props)) {
    const { href, ...linkProps } = props
    return (
      <div className="button">
        <Link className="control" to={href} {...linkProps } />
      </div>
    )
  }
  return (
    <div className="button">
      <button className="control" {...props} />
    </div>
  )
}

export default Button


Компонент принимает в качестве параметров либо ButtonProps, либо AnchorProps.

1. Если я передаю ему параметр type - он подсказывает значения button, reset и submit;
2. Если я передаю ему параметр onClick без передачи href - то событие происходит именно для HTMLButtonElement, а в противном случае для HTMLAnchorElement;

Но это реализация компонента с помощью FunctionComponent, но сколько я ни пытался - не получается это переписать на ForwardRefRenderFunction, чтобы передаваемые ref зависели от Props. Я примерно понимаю что это должно как-то реализовываться с помощью дженериков, но решение мне в голову так и не пришло.
  • Вопрос задан
  • 117 просмотров
Решения вопроса 1
WblCHA
@WblCHA
https://www.typescriptlang.org/play?#code/JYWwDg9g...

spoiler
type QQQ = {
  a?: never;
  b: string;
  c: string;
};
type WWW = {
  a: string;
  b: string;
  d: string;
};

type ForwardedRefWithOverride<T extends [object, HTMLElement]> = UnionToIntersection<
  T extends T ? ForwardRefExoticComponent<PropsWithoutRef<T[0]> & RefAttributes<T[1]>> : never
>;

const forwardRefWithOverrides = <T extends [object, HTMLElement][]>(
  render: ForwardRefRenderFunction<T[number][1], T[number][0]>,
) => {
  return forwardRef(render) as ForwardedRefWithOverride<T[number]>;
};

export const Ccc = forwardRefWithOverrides<[[QQQ, HTMLButtonElement], [WWW, HTMLLinkElement]]>((props, ref) => {
  if (typeof props.a === 'string') {
    const { a, b, d } = props;

    return (
      <>
        <link ref={ref as ForwardedRef<HTMLLinkElement>} />

        {a}
        {b}
        {d}
      </>
    );
  }
  {
    const { a, b, c } = props;

    return (
      <button ref={ref as ForwardedRef<HTMLButtonElement>} type="button">
        {a}
        {b}
        {c}
      </button>
    );
  }
});

Ccc.displayName = 'Ccc';

export const Vvv = () => {
  const ref1 = useRef<HTMLButtonElement>(null);
  const ref2 = useRef<HTMLLinkElement>(null);

  return (
    <>
      <Ccc ref={ref1} b="asd" c="asd" />
      <Ccc ref={ref2} a="asd" b="asd" d="asd" />

      {/* @ts-expect-error - c missed */}
      <Ccc b="asd" d="asd" />
      {/* @ts-expect-error - d missed, extra c */}
      <Ccc a="asd" b="asd" c="asd" />
      {/* @ts-expect-error - extra c */}
      <Ccc a="asd" b="asd" c="asd" d="asd" />
      {/* @ts-expect-error - wrong ref */}
      <Ccc ref={ref2} b="asd" c="asd" />
    </>
  );
};
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

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

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