Объединение перечислений — это не то же самое, что перечисление?

#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'.