Typescript: using conditional typing in conditional statements

寵の児 提交于 2021-01-29 07:00:57

问题


Assume I have some a-lot-of-union type:

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

where MyType{N} has this kind of signature:

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

I know that I can use a kind of type guard function, e. g.:

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

but in this case I should write a lot of this kind of functions.

So, the question is: can I dynamically define type inside a conditional statements (if ... else or switch ... case)? For example:

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:


If you plan to check a union-typed value with a switch/case statement, you should probably make it a disciminated union where the type property of each constituent of the union is declared to be the relevant string literal instead of just string. You don't really need conditional types to deal with this, at least not inside your someFunction() implementation.

For example, let's say your types look like this:

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;

Then the compiler will automatically treat checks on MyComplexType["type"] as a type guard, like this:

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
    }
}

That exhaustivenessCheck() is basically a throw statement if the function somehow falls through to default. That shouldn't happen, but the usefulness is that the compiler will warn you if it doesn't think you checked everything. That's because exhaustivenessCheck() requires its parameter to be of type never, which can't happen. If you comment out the case "type3" clause, or sometime later add a new constituent to the MyComplexType union, the exhaustivenessCheck() line will throw an error saying you failed to check a case.


At this point you could stop, but if your types are really that programmatic in that they contain just two properties, a type discriminant string and a data property, then you can define your types with less repetition like this:

// 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]

You can verify that MyType or MyType<keyof MyTypes> expands to the MyComplexType union I defined above. Your old MyType1 is now MyType<"type1">, and so forth. That is, if you need to use your old names for the types you can do it like this:

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

Hope that helps; good luck!



来源:https://stackoverflow.com/questions/56046822/typescript-using-conditional-typing-in-conditional-statements

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!