Typescript: есть ли способ изменить тип в зависимости от значения атрибута? - PullRequest
1 голос
/ 06 мая 2019

Я пытаюсь изменить тип объекта в зависимости от значения атрибута "ключ".

У меня есть несколько типов, таких как AElement, BElement, ..., ZElement. Каждый элемент имеет общий атрибут, называемый «имя». Так что в зависимости от этого атрибута тип должен быть изменен.

Например:

// Elements
type AElement = {name:"a"; propA: string;}
type BElement = {name:"b"; propB: number;}
type CElement = {name:"c"; propC: string;}
// ...
type ZElement = {name:"z"; propZ: string; propZZ: number;}

// Map interface
interface ElementsMap {
    "a": AElement;
    "b": BElement;
    "c": CElement;
    //...
    "z": ZElement
}

// My custom type
type Elem<K extends keyof ElementsMap = keyof ElementsMap> = ElementsMap[K];

// Use it
let elements:Elem[] = [{
    name: "a",
    propA: "123",
},{
    name: "c",
    propC: "321",
},{
    name: "z",
    propZ: "123",
    propZZ: 123,
}];

// Test
let test1 = elements[2];
let test2: Elem = {
    name: "a",
    propA: "123",
}

Я ожидаю, когда я использую Element[], каждый тип зависит от ключевого атрибута "name", я могу использовать различные реквизиты этого типа. Но тип переменных test1 и test2: AElement | BElement | CElement | ZElement для каждой, но я ожидаю ZElement и AElement.

1 Ответ

0 голосов
/ 07 мая 2019

К сожалению, это базовая «особенность» «Массивов», и она работает так, как задумано, индексирование массива по определенному индексу всегда приведет к тому типу Массивов, даже если этот тип является объединением.

Этотакже всегда будет выдавать тип Arrays независимо от того, какое значение имеет этот индекс,

Индексировать можно только по кортежу по ключу, чтобы получить тип по этому ключу, и это потому, что они имеют длину, известную во время выполнения где-какМассивы этого не делают.

Возьмем следующий код в качестве примера.

const testTuple: [AElement, CElement, ZElement] = [{
    name: "a",
    propA: "123",
},{
    name: "c",
    propC: "321",
},{
    name: "z",
    propZ: "123",
    propZZ: 123,
}];
const TestTypeAtIndex = testTuple[0] // AElement and not a union.

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

// use it as array.
const testTuple: Elem[] = [{
    name: "a",
    propA: "123",
},{
    name: "c",
    propC: "321",
},{
    name: "z",
    propZ: "123",
    propZZ: 123,
}];

const isAElement = (ele: ElementsMap[keyof ElementsMap]): ele is AElement => {
    return "propA" in ele;
}

const testTypeAtIndex = testTuple[0] // union of everything
if(isAElement(testTypeAtIndex)) {
    const newTestType = testTypeAtIndex; // inside here its JUST AElement because of typeguard.
}
...