#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