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