首页 > 解决方案 > 打字稿:在条件语句中使用条件类型

问题描述

假设我有很多联合类型:

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

哪里MyType{N}有这种签名:

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

我知道我可以使用一种类型保护功能,例如:

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

但在这种情况下,我应该写很多这样的函数。

所以,问题是:我可以在条件语句(if ... elseswitch ... 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
        ...
    }
}

标签: typescripttypesconditional-types

解决方案


如果您打算使用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()要求它的参数是 type 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]

您可以验证MyTypeMyType<keyof MyTypes>扩展到MyComplexType我上面定义的联合。你的旧MyType1是现在MyType<"type1">,等等。也就是说,如果您需要为类型使用旧名称,您可以这样做:

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

希望有帮助;祝你好运!


推荐阅读