Как использовать компонент с предоставленным ref в React.forwardRef, указав его имя в виде строки?

Как реализовать компонент `Button`, используя `React.forwardRef`, чтобы дать возможность выбора отображаемого элемента (например, кнопки или ссылки), в зависимости от переданных параметров, и связать это с ref, принимая разные типы ссылок (`React.Ref<HTMLButtonElement>` или `React.Ref<HTMLAnchorElement>`) в зависимости от переданных параметров?
  • 29 февраля 2024 г. 19:18
Ответы на вопрос 2
Для использования компонента с предоставленным ref в React.forwardRef, нужно передать ref в дочерний компонент, используя атрибут `ref`, и передать этот ref внутреннему компоненту через `React.forwardRef`.

Пример реализации компонента `Button` с использованием `React.forwardRef`:

```jsx
import React from 'react';

const Button = React.forwardRef(({ href, children, ...rest }, ref) => {
  const linkButton = href ? 'a' : 'button';

  return React.createElement(
    linkButton,
    {
      ...rest,
      ref,
      href: href || null
    },
    children
  );
});

export default Button;
```

В данном примере компонент `Button` принимает параметры `href`, `children` и `rest`, а также переданный ref. В зависимости от того, передан ли `href`, компонент создает элемент `a` (для ссылки) или `button` (для кнопки) и передает ему все параметры.

Использование компонента `Button` может выглядеть следующим образом:

```jsx
import React, { useRef } from 'react';
import Button from './Button';

const App = () => {
  const buttonRef = useRef(null);

  const handleClick = () => {
    buttonRef.current.focus();
  }

  return (
    <div>
      <Button ref={buttonRef} onClick={handleClick}>Click me</Button>
      <Button ref={buttonRef} href="https://example.com">Go to example.com</Button>
    </div>
  );
}

export default App;
```

В данном примере компонент `Button` позволяет использовать как кнопку с текстом "Click me", так и ссылку на "example.com". И в обоих случаях можно передавать и использовать ref, указывая на элемент `button` или `a` в зависимости от переданных параметров.
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...
Похожие вопросы