Typescript: использование условной типизации в условных выражениях - PullRequest
0 голосов
/ 08 мая 2019

Предположим, у меня есть какой-то тип объединения:

var MyComplexType = MyType1 | MyType2 | MyType3 | ... | MyTypeN

где MyType{N} имеет такую ​​подпись:

type MyType1 = {
    type: string,
    data: <different data for different types>
}

Я знаю, что могу использовать функцию защиты типа, e. g.:

function isMyComplexTypeOfMyType1(item: MyComplexType): item is MyType1 {
    return item.type == "type of MyType1"
}

но в этом случае я должен написать множество таких функций.

Итак, вопрос в том, могу ли я динамически определять тип внутри условных операторов (if ... else или switch ... case)? Например:

function someFunction(item: MyComplexType) {
    switch (item.type) {
        case "type of MyType1":
            // item is MyType1
            // do something
            break
        case "type of MyType2":
            // item is MyType2
            // do something
            break
        ...
    }
}

1 Ответ

1 голос
/ 08 мая 2019

Если вы планируете проверять значение типа объединения с помощью оператора switch/case, вы, вероятно, должны сделать его исключенным объединением , где свойство type каждого компонента объединения объявлено каксоответствующий строковый литерал вместо просто string.Вам на самом деле не нужны условные типы, чтобы справиться с этим, по крайней мере, не внутри вашей someFunction() реализации.

Например, скажем, ваши типы выглядят так:

type MyType1 = { type: "type1", data: { a: string, b: number } };
type MyType2 = { type: "type2", data: { c: boolean, d: string } };
type MyType3 = { type: "type3", data: { e: number, f: boolean } };

type MyComplexType = MyType1 | MyType2 | MyType3;

Тогдакомпилятор будет автоматически обрабатывать проверки MyComplexType["type"] как охрану типа, например:

const exhaustivenessCheck = (x: never) => x;

function someFunction(item: MyComplexType) {
    switch (item.type) {
        case "type1":
            console.log(2 * item.data.b); // okay
            break;
        case "type2":
            console.log(item.data.d.charAt(0)); // okay
            break;
        case "type3":
            console.log(7 - item.data.e); // okay
            break;
        default:
            throw exhaustivenessCheck(item); // okay
    }
}

То, что exhaustivenessCheck(), по сути, является оператором throw, если функция каким-то образом падает до default.Этого не должно случиться, но полезность заключается в том, что компилятор предупредит вас, если не решит, что вы все проверили.Это потому, что exhaustivenessCheck() требует, чтобы его параметр имел тип never, чего не может быть.Если вы закомментируете предложение case "type3" или когда-нибудь позже добавите новый компонент в объединение MyComplexType, строка exhaustivenessCheck() выдаст ошибку, сообщающую, что вы не проверили регистр.


На этом этапе вы можете остановиться, но если ваши типы действительно настолько программные, поскольку они содержат только два свойства: type дискриминантную строку и свойство data, то вы можете определить свои типы с меньшим количеством повторений, например так:

// a mapping from type string to data type
type MyTypes = {
    type1: { a: string, b: number };
    type2: { c: boolean, d: string };
    type3: { e: number, f: boolean };
}

// convert the mapping to the union of types
type MyType<K extends keyof MyTypes = keyof MyTypes> = {
    [P in K]: { type: P, data: MyTypes[P] }
}[K]

Вы можете проверить, что MyType или MyType<keyof MyTypes> расширяется до объединения MyComplexType, которое я определил выше.Ваш старый MyType1 теперь MyType<"type1"> и так далее.То есть, если вам нужно использовать ваши старые имена для типов, вы можете сделать это следующим образом:

type MyType1 = MyType<"type1">;
type MyType2 = MyType<"type2">;
type MyType3 = MyType<"type3">
type MyComplexType = MyType;

Надеюсь, это поможет;удачи!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...