高级类型 可辨识联合(Discriminated Unions) 你可以合并单例类型,联合类型,类型保护和类型别名来创建一个叫做 可辨识联合的高级模式,它也称做 标签联合或 代数数据类型。 可辨识联合在函数式编程很有用处。 一些语言会自动地为你辨识联合;而TypeScript则基于已有的JavaScript模式。 它具有3个要素:
具有普通的单例类型属性— 可辨识的特征。
一个类型别名包含了那些类型的联合— 联合。
此属性上的类型保护。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 interface Interface1 { kind : 'interface1' , property1 : number , } interface Interface2 { kind : 'interface2' , property2 : number , property3 : number , } interface Interface3 { kind : 'interface3' , property4 : number , property5 : number , }
首先我们声明了将要联合的接口。 每个接口都有 kind属性但有不同的字符串字面量类型。 kind属性称做 可辨识的特征或 标签。 其它的属性则特定于各个接口。 注意,目前各个接口间是没有联系的。 下面我们把它们联合到一起:
1 type Type = Interface1 | Interface2 | Interface3 ;
现在我们使用可辨识联合:
1 2 3 4 5 6 7 8 9 10 function getType (i : Type ) { switch (i.kind ) { case "interface1" : return i.property1 * i .property1 ; case "interface2" : return i.property2 * i.property3 ; case "interface3" : return i.property4 * i.property5 ; } }
完整性检查 当没有涵盖所有可辨识联合的变化时,我们想让编译器可以通知我们。 比如,如果我们添加了Interface4到Type,我们同时还需要更新getType:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 interface Interface4 { kind : 'interface4' , property6 : number , } type Type = Interface1 | Interface2 | Interface3 | Interface4 ;function getType (i : Type ) { switch (i.kind ) { case "interface1" : return i.property1 * i .property1 ; case "interface2" : return i.property2 * i.property3 ; case "interface3" : return i.property4 * i.property5 ; } }
有两种方式可以实现。 首先是启用 –strictNullChecks并且指定一个返回值类型:
1 2 3 4 5 6 7 8 9 10 function getType (i : Type ): number { switch (i.kind ) { case "interface1" : return i.property1 * i .property1 ; case "interface2" : return i.property2 * i.property3 ; case "interface3" : return i.property4 * i.property5 ; } }
因为 switch没有包涵所有情况,所以TypeScript认为这个函数有时候会返回 undefined。 如果你明确地指定了返回值类型为 number,那么你会看到一个错误,因为实际上返回值的类型为 number | undefined。 然而,这种方法存在些微妙之处且 –strictNullChecks对旧代码支持不好。第二种方法使用 never类型,编译器用它来进行完整性检查:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function assertNever (x : never ): never { throw new Error ("Unexpected object: " + x); } function getType (i : Type ): number { switch (i.kind ) { case "interface1" : return i.property1 * i .property1 ; case "interface2" : return i.property2 * i.property3 ; case "interface3" : return i.property4 * i.property5 ; default : return assertNever (i); } }
这里, assertNever检查 s是否为 never类型—即为除去所有可能情况后剩下的类型。 如果你忘记了某个case,那么 s将具有一个真实的类型并且你会得到一个错误。 这种方式需要你定义一个额外的函数,但是在你忘记某个case的时候也更加明显。**Tips** 上面的代码是根据官网的逻辑写的,奇怪的是最后一步居然在编译的时候报错了。报错信息如下
1 2 3 4 5 $ tsc src/advanced_types_4.ts src/advanced_types_4.ts:38:26 - error TS2345: Argument of type 'Interface4' is not assignable to parameter of type 'never' . 38 return assertNever(i); // error here if there are missing cases ~