Как присоединить составной компонент при использовании React forward ref (свойство не существует в forwardRefExoticComponent)

#reactjs #typescript

#reactjs #typescript

Вопрос:

У меня есть компонент панели, который при необходимости позволяет пользователю использовать составные компоненты. Панель имеет заголовок по умолчанию, который содержит функцию закрытия (кнопка), и основная причина, по которой я это сделал, заключалась в том, чтобы предоставить пользователю возможность использовать пользовательский заголовок со своей собственной кнопкой и все остальное, что он хотел бы, поэтому я решил использовать составные компоненты и разрешить компоненту панели проверятьдля существования компонента в его дочерних элементах, и если это так, он не будет использовать заголовок по умолчанию. У меня все работает, но я не могу понять, как очистить ошибку Typescript.

Ниже приведен код, относящийся к Typescript, и ошибка, которую я получаю. Я также связался с изолированной средой с рабочим кодом с ошибками.

Я перепробовал множество вариантов поиска в Google и через несколько дней не смог найти решение.

Песочница: https://codesandbox.io/s/panel-513yq?file=/src/Panel/Panel.tsx:510-4061

App.js:

 export default function App() {
  const [open, setOpen] = React.useState(false);

  const handleClose = () => {
    setOpen(false);
  };

  const handleOpen = () => {
    setOpen(true);
  };

  return (
    <div id="page" data-test-id="component-app">
      <header id="header">
        <button onClick={handleOpen}>Open</button>
      </header>
      <main id="main">
        <Panel open={open} onClosed={handleClose} renderPortal={true}>
          <Panel.Header>
            <div>
              <button onClick={handleClose}>Close</button>
            </div>
          </Panel.Header>
          <Panel.Content>Panel Content</Panel.Content>
        </Panel>
      </main>
    </div>
  );
}
 

Компонент панели:

 interface ModalPropsComposition {
  Header?: React.FC<PanelHeaderProps>;
  Content?: React.FC<PanelContentProps>;
}

export interface PanelProps
  extends React.HTMLAttributes<HTMLDivElement>,
    ModalPropsComposition {
  open: boolean;
  position?: string;
  renderPortal?: boolean;

  // Lifecycle callbacks
  onClose?: () => void;
  onClosed?: () => void;
  onOpen?: () => void;
  onOpened?: () => void;
}

/**
 * @Codeannex UI React: Panel Component
 *
 * Panel Component
 */
const _Panel = ({
  children,
  open,
  renderPortal,
  onClose,
  onClosed,
  onOpen,
  onOpened
}: PanelProps amp; { forwardedRef: React.Ref<HTMLDivElement> }): JSX.Element => {
  // panel code...
};

export const Panel = React.forwardRef(
  (props: PanelProps, ref: React.Ref<HTMLDivElement>): JSX.Element => {
    return <_Panel {...props} forwardedRef={ref} data-testid={PANEL_TEST_ID} />;
  }
);

Panel.Header = PanelHeader;
Panel.Content = PanelContent;
 

Ошибка:

введите описание изображения здесь

Комментарии:

1. Если вы просто хотите, чтобы ошибка была устранена, сделайте export const Panel: any это в Panel.tsx, ошибка в значительной степени понятна, выполнив Panel.Content = PanelContent и PanelHeader = PanelHeader typescript жалуется, поскольку свойства Content и Header являются пользовательскими и не существуют для типа forwardRef по умолчанию.

Ответ №1:

Выполнив в Panel.tsx

 Panel.Content = PanelContent;
Panel.Header = PanelHeader 
 

typescript жалуется, поскольку свойства Content и Header являются пользовательскими и не существуют для базового типа forwardRef .

Чтобы исправить это, объявите пользовательский интерфейс, который расширяет базовый тип forwardRef, и добавьте свои пользовательские свойства Заголовок и содержимое:

 interface IPanel
  extends React.ForwardRefExoticComponent<
    PanelProps amp; React.RefAttributes<HTMLDivElement>
  > {
  Header: typeof PanelHeader;
  Content: typeof PanelContent;
}
 

и используйте его следующим образом:

 const forwardRef = React.forwardRef<HTMLDivElement, PanelProps>(
  (props, ref): JSX.Element => {
    return (
      <PanelComponent
        {...props}
        forwardedRef={ref}
        data-testid={PANEL_TEST_ID}
      />
    );
  }
);

export const Panel = {
  ...forwardRef,
  Header: PanelHeader,
  Content: PanelContent
} as IPanel;
 

Или … просто сделайте, export const Panel: any но при этом вы потеряете проверку типа

Рабочий CodeSandbox разветвлен на ваш.

Комментарии:

1. Спасибо. Однако каждая библиотека, которую я использовал в прошлом, не заставляла разработчика запускать эту панель проверки. Заголовок amp;amp; . Я хотел бы найти способ сохранить общие шаблоны, используемые при использовании составных компонентов.

2. @Aaron обновил ответ и поле CodeSandbox, поэтому вам больше не нужно запускать проверку.

3. Спасибо, что нашли время для ответа. Этот ответ был очень полезен.

Ответ №2:

Вы можете исправить эту ошибку, используя Object.assign . Это работает, потому что Object.assign вводится как assign<T, U>(target: T, source: U): T amp; U; , поэтому могут быть выведены типы T и U , а возвращаемый тип становится смесью типа экзотического компонента forwardRef и объекта со Header Content свойствами и .

 export const Panel = Object.assign(
  React.forwardRef(
    (props: PanelProps, ref: React.Ref<HTMLDivElement>): JSX.Element => {
      return (
        <PanelComponent
          {...props}
          forwardedRef={ref}
          data-testid={PANEL_TEST_ID}
        />
      );
    }
  ),
  {
    Header: PanelHeader,
    Content: PanelContent
  }
);
 

Комментарии:

1. Спасибо, что нашли время для ответа. Этот ответ был очень полезен.