#javascript #typescript #enums
#javascript #typescript #перечисления
Вопрос:
У меня есть некоторые объекты, которые имеют много общих свойств, но затем имеют небольшое подмножество, которое отличается. Они различаются по их идентификатору, который является одним значением из перечисления. Я хотел бы ввести их как подтипы одного и того же универсального, чтобы воспользоваться защитой типов, чтобы знать, какие свойства доступны.
Пример:
enum ItemIDs {
ITEM_TYPE_1,
ITEM_TYPE_2
}
// Generic item with shared properties
interface GenericItem<ID, Data> {
id: ID
data: Data
}
// Specific items where the 'data' property can be different shapes
type SpecificItemOne = GenericItem<ItemIDs.ITEM_TYPE_1, { content: string }>
type SpecificItemTwo = GenericItem<ItemIDs.ITEM_TYPE_2, { amount: number }>
// Specific item is a union of all specific items
type SpecificItem = SpecificItemOne | SpecificItemTwo;
// Take item and test that typescript can work out what properties are available
// It works!
const testTypeGuard = (item: SpecificItem) => {
if (item.id === ItemIDs.ITEM_TYPE_1) {
item.data.content = ''
} else if (item.id === ItemIDs.ITEM_TYPE_2) {
item.data.amount = 0;
}
return item;
}
// Try to create item where ID can be any from ID enum
const breakTypeGuard = (id: ItemIDs, data: any) => {
// Type 'ItemIDs' is not assignable to type 'ItemIDs.ITEM_TYPE_2'.
// WHY
testTypeGuard({ id, data });
}
Или <a rel="noreferrer noopener nofollow" href="https://www.typescriptlang.org/play/#src=enum ItemIDs {
ITEM_TYPE_1,
ITEM_TYPE_2
}
// Generic item with shared properties
interface GenericItem {
id: ID
data: Data
}
// Specific items where the ‘data’ property can be different shapes
type SpecificItemOne = GenericItem
type SpecificItemTwo = GenericItem
// Specific item is a union of all specific items
type SpecificItem = SpecificItemOne | SpecificItemTwo;
// Take item and test that typescript can work out what properties are available
// It works!
const testTypeGuard = (item: SpecificItem) => {
if (item.id === ItemIDs.ITEM_TYPE_1) {
item.data.content = »
} else if (item.id === ItemIDs.ITEM_TYPE_2) {
item.data.amount = 0;
}
return item;
}
// Try to create item where ID can be any from ID enum
const breakTypeGuard = (id: ItemIDs, data: any) => {
// Type ‘ItemIDs’ is not assignable to type ‘ItemIDs.ITEM_TYPE_2’.
// WHY
testTypeGuard({ id, data });
}
» rel=»nofollow noreferrer»>интерактивный на сайте ts.
Кажется, это говорит о том, что он не может присвоить все значения enum определенному подтипу. Я не понимаю, почему это проблема, потому что это объединение с другими типами, которые вместе принимают все значения enum.
Что я делаю не так?
Спасибо за любую помощь.
Ответ №1:
Проблема в том, что когда вы отправляете { id, data }
в качестве своего параметра, это рассматривается как объектный литеральный тип.
// This function you declared is expecting a SpecificItem type parameter
// and you are sending an object literal type parameter
const testTypeGuard = (item: SpecificItem) => {
if (item.id === ItemIDs.ITEM_TYPE_1) {
item.data.content = ''
} else if (item.id === ItemIDs.ITEM_TYPE_2) {
item.data.amount = 0;
}
return item;
}
Следовательно, типы не совпадают, и именно поэтому вы получаете сообщение об ошибке.
Что вам нужно сделать, это отправить объект с указанием типа, как предложил @przemyslaw-pietrzak, вот так:
// Try to create item where ID can be any from ID enum
const breakTypeGuard = (id: ItemIDs, data: any) => {
// Type 'ItemIDs' is not assignable to type 'ItemIDs.ITEM_TYPE_2'.
// WHY
testTypeGuard({ id, data } as SpecificItem);
}
Ответ №2:
На мой взгляд, в вашем коде нет ничего плохого. TS иногда не оценивает типы (вероятно, из-за проблем с производительностью).
Если вы хотите, чтобы этот код работал, я предлагаю добавить testTypeGuard({ id, data } as SpecificItem);
. Это не очень небезопасно, потому что TS не позволяет сопоставлять все типы. Например:
let fn = (arg: 42) => 42;
fn(1); // Argument of type '1' is not assignable to parameter of type '42'.
fn(1 as number) // Argument of type 'number' is not assignable to parameter of type '42'.