问题
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