Как использовать компонент с предоставленным 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` в зависимости от переданных параметров.
<pre><code class="javascript">type ButtonType = 'button' | 'a';

interface ButtonTypeProps extends Record&lt;ButtonType, object&gt; {
  button: {};
  a: { href: string };
}

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

type Props&lt;T extends ButtonType&gt; = ButtonTypeProps[T] &amp; CommonProps &amp; ComponentPropsWithoutRef&lt;T&gt;;

type ButtonComponent&lt;T extends ButtonType = ButtonType&gt; = UnionToIntersection&lt;
  T extends T ? ForwardRefExoticComponent&lt;Props&lt;T&gt; &amp; RefAttributes&lt;ComponentRef&lt;T&gt;&gt;&gt; : never
&gt;;

const isLinkProps = (props: Props&lt;ButtonType&gt;): props is Props&lt;'a'&gt; =&gt; 'href' in props;

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

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

Button.displayName = 'Button';</code></pre> <br/> <br/> <a href="https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAJQKYEMDGMA0cDeAoOOGATzCTgGEJwIA7JWmABSgjAGcB1YGACwgCuMZADNMBIqXJUa9RqPGESZOADFoAdxRQAJqICiADwgxgaGZDlYJy8qICCMGFGAAjIUnaK4Izdr1IYjZSan66SAFBAL4+rCBwAORQqBgJANx4eEiGkLCSKgCqtMB0ACoQAJKMSFDsSBgltAA8BQB8cAC8cAAUBXDZMAw67HB9APw92gDmAFyjAJSd7QBuEMA6cHP0yzWLA0Mj3dNzwLQiNXAVix0razoSExUSW0g7UBl4tnAAQkIwZSEugl3E46Ak4AAfRIodKZU6DKAidDkX6g2ilKQsNgjfa0YaIerQHRNVH-dFSbAQVwAK3qMHa+EIILJcxwUQyhBQrLgvGSIjm7GcpymcHZeCimS+MhAdCxHE6uAkaF4wAANjpkrQ5sh0DAAHQ6jAAOQgOiQHLgy20wBQjDGcwSYFVKFO4KhCTJKFVbsSIjVqoiPoSghgqtOgYt1IEguAImI9sSgu0MCDaAYCKDQ1hYs+ITl7CapX6hkGeJGpIBZHaXQr5LI+YA2qUALpwABklGoMto+fbndk6fz3D4IdEhdaHy+tYsdHTheLpfxtYxKhrf0rSGro2KZUq1VqdMaTQkRdx+KLE3UUC0ugMxlM5moljn+fHfYcThcIM8TRnVjHpStEBmxwNsNR4BOmRoHQgpwMA7AADKnAA1r2XTdGArAcHMr7LlIrTzHMmHYnBIyvgkMLVu0CS8oE4KnHAxEcB80G0LBtYKr417+KI3Tzme5brnW5BrmiK6bhhWHsDhUnjtgfJzFeN5mpEv5PrO8iBOOBFLIqhCxj08FIbQqFSZJ2LzIsjKEHAyQwAIUC0D0Eg2XATQoLZgQdDgfJwCgIxKf4ERjn+6ZjhRCRATEtEiN5THsHqMUxGgzrsOwRooCASAdAARMydA5a0Lmubg8V6sqaoagwEolW5AD0KBFa58wWhKEh2Q5TndMVTT5U5fLeb5-mhNx4SqaFmkiE0wJCZFrQxLYuV9TlcApf56WZdleVCYVxWEDgZUVeqmo1TZTR1X1TVwC14qLMN07qVYHy1nqOjwU6KDEBlWUKgktawkAA" rel="nofollow">https://www.typescriptlang.org/play?#code/JYWwDg9g...</a>
Похожие вопросы