我想要if条件取决于参数类型。
在下面的例子中,typescript无法识别如果我通过";猫;作为type
参数,则canMeow
应在if条件下可用。
有什么类型的安全方法可以实现以下目的吗?
type Cat = {
name: string;
canMeow: boolean;
}
type Dog = {
name: string;
canBark: boolean
}
type TypeMap = {
dog: Dog;
cat: Cat;
}
type Animal<T> = {
type: T;
data: TypeMap[T]
}
function canMakeSound<Type extends keyof TypeMap>(animal: Animal<Type>){
if(animal.type == "cat"){
return animal.data.canMeow; // <--- error
}
else if(animal.type == "dog"){
return animal.data.canBark; // <--- error
}
}
canMakeSound({type: "cat", data: {name: "kitty", canMeow: true}});
TypeScript中目前没有通过控制流分析缩小泛型类型参数的机制。也就是说,如果animal
是泛型类型T
,那么检查animal.type
可能有助于缩小animal.type
,但它将而不是缩小或重新约束T
本身。因此,对于编译器不知道的T
,animal.data
将保持为TypeMap[T]
。这里有各种悬而未决的问题需要改进,比如microsoft/TypeScript#33014,但目前它还不是语言的一部分。
因此,与其给animal
一个泛型类型,不如考虑编译器通过检查animal.type
来缩小animal
的类型。。。即受歧视的联盟。以下是如何生成这种类型:
type Animal = { [K in keyof TypeMap]: {
type: K;
data: TypeMap[K]
} }[keyof TypeMap]
/* type Animal = {
type: "dog";
data: Dog;
} | {
type: "cat";
data: Cat;
} */
这里Animal
是通过立即索引到映射类型中创建的,这使其成为上面所示的并集类型。type
是文字类型"dog"
,data
将是Dog
,或者type
是文字类型"cat"
,data
将是Cat
。这使得type
成为Animal
的判别式属性,如果您检查该属性,它将把整个对象的类型缩小到仅符合检查的联合成员:
function canMakeSound(animal: Animal) {
if (animal.type == "cat") {
return animal.data.canMeow; // okay
}
else if (animal.type == "dog") {
return animal.data.canBark; // okay
}
throw new Error(); // added this due to microsoft/TypeScript/issues/21985
}
这一切都很好,只是我添加了一行throw
来安抚编译器;它无法从那些if
/else
语句中判断出animal.type
检查是详尽无遗的,因此它抱怨您从一开始就没有return
。这是microsoft/TypeScript#21985上的一个已知问题。
让我们测试一下:
console.log(
canMakeSound({ type: "cat", data: { name: "kitty", canMeow: true } })
); // true
canMakeSound({ type: "dog", data: { name: "kitty", canMeow: true } }); // error!
// ----------------------------------------------> ~~~~~~~~~~~~~
// Type '{ name: string; canMeow: boolean; }' is not assignable to type 'Dog'
看起来不错。它的行为与预期的一样,如果传入不符合正确type
的data
,编译器会发出警告。
游乐场链接到代码