《TypeScript实战指南》—2.2.2 交叉类型与联合类型
2.2.2 交叉类型与联合类型
通常意义上,我们所说的交叉类型是指将多个字典类型合并为一个新的字典类型。
基本类型是不会存在交叉的。比如 number 和 string 是不可能有交叉点的,一个类型不可能既是字符串又是数字。所以当我们使用交叉类型时通常是下面这样:
type newType = number & string;
let a: newType;
interface A {
d: number,
z: string,
}
interface B {
f: string,
g: string,
}
type C = A & B
let c: C
这里的 type 关键字是用来声明类型变量的。在运行时,与类型相关的代码都会被移除掉,并不会影响到 JavaScript 的执行。
但如果交叉类型中有属性冲突时,比如下列代码:
let a: newType;
interface A {
d: number,
z: string,
}
interface B {
d: string,
g: string,
}
type C = A & B
let c: C
c.d = 1;
// [ts]
// Type '1' is not assignable to type 'number & string'.
// Type '1' is not assignable to type 'string'.
// (property) d: number & string
c.d = "123";
// [ts]
// Type '"123"' is not assignable to type 'number & string'.
// Type '"123"' is not assignable to type 'number'.
// (property) d: number & string
这就非常滑稽了,d 无论如何赋值都不可能通过类型检查。
但当我们正确使用交叉类型时,它可以帮我们合理地将两个不同类型叠加为新的类型,并包含了所需的所有类型。
那么, 如果我们需要一个变量可能是 number,也有可能是 string,该如何表达呢?这也是一个很常见的场景,联合类型便是用于解决这样的问题。比如,下面这段经典的函数:
function padLeft(value: string, padding: any) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
padLeft("Hello world", 4); // " Hello world"
padLeft存在一个问题,padding参数的类型被指定为any。也就是说,我们可以传入一个既不是number也不是string类型的参数,但是TypeScript却不报错:
let indentedString = padLeft("Hello world", true); // 编译阶段通过,运行时报错
在传统的编程语言里,我们可以使用重载来解决这样的问题。
但在 JavaScript的世界里,并没有重载可以使用,手动判断类型操作更常见。这在一定程度上避免了过度设计。
如果我们希望更准确地描述 padding 的类型,那就只能让 padding 既可以是 number 又可以是 string,所以联合类型就非常有必要了。
替代掉 any,我们可以使用联合类型作为padding的参数,如下所示:
function padLeft(value: string, padding: string | number) {
// ...
}
let indentedString = padLeft("Hello world", true);
// [ts] Argument of type 'true' is not assignable to parameter of type 'string | number'.
联合类型表示一个变量可以是几种类型之一。我们用竖线 | 分隔每个类型,所以number | string | boolean表示一个值可以是number、string或boolean。
请记住,如果一个值是联合类型,我们只能访问它们共有的属性。
这说来有点复杂,我们可以来看一下 interface 相关的例子:
interface A {
a: number,
b: string,
}
interface B {
b: string,
c: number,
}
interface C {
b: string,
f: number,
}
let obj: A | B | C;
obj.a = 1;
// [ts]
// Property 'a' does not exist on type 'A | B | C'.
// Property 'a' does not exist on type 'B'.
obj.b = '';
在 interface中,联合类型取的是交集,交叉类型取的是并集。这听上去跟名字有些冲突,然而它们在基本类型又不是这样表现的。
如果你仔细阅读上面的例子,还会发现一个问题,就是上面的代码如果直接运行的话是会报错的。所以还需谨记,TypeScript 只会帮你在编译时做类型检查,并不确保你的代码在运行时中的安全。
- 点赞
- 收藏
- 关注作者
评论(0)