Компонент React, отправляющий общую полезную нагрузку

#reactjs #typescript

#reactjs #typescript

Вопрос:

Я пытаюсь создать повторно используемый <Column /> компонент, который отображает список элементов, каждый из которых отправляет общую полезную нагрузку, указанную вызывающим абонентом при нажатии.

Мой столбец принимает onItemClick prop, который является функцией, которая отправляет полезную нагрузку (действие Redux в моем реальном коде). Я хочу, чтобы моя функция могла принимать и отправлять общую <PayloadType> :

 type ColumnProps<PayloadType> = {
  menuItems: { name: string; id: number }[];
  onItemClick: (payload: PayloadType) => void;
};

const Column = <PayloadType extends {}>(
  props: React.PropsWithChildren<ColumnProps<PayloadType>>
) => {
  const { menuItems, onItemClick } = props;
  const handleButtonClick = (menuItem: MenuItem) => {
    onItemClick({ info: menuItem.name });
    /* 
     Argument of type '{ info: string; }' is not assignable to parameter of type 
     'PayloadType'.
     '{ info: string; }' is assignable to the constraint of type 'PayloadType', but 
     'PayloadType' could be instantiated with a different subtype of constraint '{}'
    */
  };
  return (
    <>
      {menuItems.map((menuItem, index) => (
        <button key={index} onClick={(event) => handleButtonClick(menuItem)}>
          {menuItem.name}
        </button>
      ))}
    </>
  );
};
  

Использование компонента:

 type MenuItem = {
  name: string;
  id: number;
};

const testMenuItems: MenuItem[] = [
  { name: "Vanilla", id: 0 },
  { name: "Strawberry", id: 1 },
  { name: "Chocolate", id: 2 },
  { name: "Cookies amp; Cream", id: 3 }
];

type ColumnPayload = {
  info: string;
};

export default function App() {
  const columnClickHandler = (payload: ColumnPayload) => {
    console.log(`clicked: ${payload.info}`);
  };
  return (
    <div className="App">
      <Column<ColumnPayload>
        menuItems={testMenuItems}
        onItemClick={columnClickHandler}
      />
    </div>
  );
}
  

Как видно выше, я получаю сообщение об ошибке:

 Argument of type '{ info: string; }' is not assignable to parameter of type 'PayloadType'.
  '{ info: string; }' is assignable to the constraint of type 'PayloadType', but 'PayloadType' could be instantiated with a different subtype of constraint '{}'.
  

Как я могу принять и отправить общую полезную нагрузку из моего компонента? Я довольно новичок в TypeScript, поэтому я не уверен, что я что-то упускаю или просто неправильно подхожу к проблеме.

Изолированная среда:https://codesandbox.io/s/sweet-kalam-tt5u5?file=/src/App.tsx

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

1.Ошибка заключается в том, что вы пытаетесь использовать определенный тип полезной нагрузки ColumnPayload внутри тела вашей общей функции, которую вы вызываете onItemClick({ info: menuItem.name }) ; Ошибка может быть исправлена в вашем примере, сделав столбец не общим и зафиксировав его только ColumnPayload . Конечно, это, вероятно, не то решение, которое вам нужно — не могли бы вы, пожалуйста, изменить свой пример, чтобы он показывал проблему, возникающую, когда мы просто делаем столбец нестандартным?

2. Я думаю, что нет проблем, если я сделаю столбец не универсальным, кроме того факта, что onItemClick теперь он привязан к объекту с определенной формой ColumnPayload . Как вы упомянули в своем ответе, мой компонент Column больше не используется повторно, что было основной причиной моей попытки использовать дженерики. Возможно, я неправильно подхожу к этому, но я не уверен, как еще создать повторно используемый компонент, который может вызывать общую функцию.

3. Возможно, тогда вам следует передать onItemClick в качестве реквизита.

Ответ №1:

Проблема здесь в том, Column что невозможно создать определенный тип без посторонней помощи, он может только знать, что существует какой-то неизвестный тип.

Типовые пересечения

При этом один из способов добиться вашего общего обратного вызова — это просто объединить тип полезной нагрузки с пунктом меню.

type MenuItemWithPayload<TPayload> = MenuItem amp; Payload

Затем отправьте обратный вызов со всем элементом меню. Я привел несколько примеров кода, обратите внимание, как это ColumnMenuItem относится как к полезной нагрузке, так и к типу menuitem? это позволяет выводить типы, если вам больше не нужно определять тип полезной нагрузки при использовании компонента столбца.

https://codesandbox.io/s/eager-framework-pu4qe?file=/src/App.tsx.

Общее поле полезной нагрузки

Более чистой альтернативой может быть разрешение элементу меню содержать поле полезной нагрузки. Который похож на тип объединения, но использует композицию.

type MenuItem<TPayload = unknown> = { name: string; id: number; payload: TPayload }

https://codesandbox.io/s/friendly-currying-g1ynk?file=/src/App.tsx

Меню пересылки

Наконец, вы можете просто переслать пункт меню в обратном вызове и позволить родительскому компоненту генерировать необходимую полезную нагрузку.

https://codesandbox.io/s/hardcore-surf-ihz96?file=/src/App.tsx