基本语法
定义
在TypeScript中,联合类型(Union Types)是一种强大的类型表达形式,它允许你声明一个变量可以是多种类型之一。以下是对联合类型的详细解释、定义和赋值举例说明:
定义联合类型
联合类型通过竖线 |
符号来分隔不同的类型,这样定义的变量可以在其生命周期内持有任何列出的类型值。
1 // 定义一个联合类型变量
2 let value: string | number;
3
4// 这意味着 `value` 可以是 string 类型
5 value = 'Hello, World!';
6
7// 也可以是 number 类型
8 value = 42;
如果定义了一个联合类型的变量但没有赋值或进行类型断言,那么在编译阶段,TypeScript会认为该变量具有所有可能的类型,即联合类型本身。然而,在运行时,由于JavaScript的动态特性,未经初始化的变量默认值为undefined
。所以初始定义联合类型的类型顺序和默认值没有关系
1 let value: string | number;
2
3// 编译阶段,TypeScript会推断value可能是string或number类型
4// 但在运行时,未经初始化的value实际上是undefined
5 console.log(value);// 输出 undefined
6
7// 在这种状态下,尝试访问联合类型中任一类型的特有属性或方法都会导致编译错误
8// 因为undefined没有这些属性或方法
9// value.toUpperCase(); // 错误,因为undefined上调用toUpperCase方法不合法
10// value.toFixed(2); // 错误,同样原因
赋值与类型推断
当你给联合类型的变量赋值时,TypeScript编译器会基于所赋值的类型进行类型推断,其类型为所赋的合法值的类型:
1// 初始化时未赋值
2 let myValue: string | number;
3
4// 此时,尽管没有明确赋值,但在运行时它会有任意类型,但编译时会提示错误,因为没有初始化
5// 若要消除错误,需要显式赋值
6 myValue = 'initial value as string';
7 console.log(myValue);// 输出 "initial value as string"
8
9// 更改值为 number 类型
10 myValue = 100;
11 console.log(myValue);// 输出 100
检查类型和断言:
-
使用类型断言:类型断言是一种告诉编译器变量的具体类型的方法。它有两种语法形式:尖括号语法和 as 操作符。
- 尖括号语法:
let someValue: any = "this is a string"; let strLength: number = (<string>someValue).length;
- as 操作符:
let someValue: any = "this is a string"; let strLength: number = (someValue as string).length;
类型断言告诉编译器“相信我,我知道这个值的具体类型是什么”,因此它可以用于处理编译器无法推断出的类型或者处理类型转换的情况。
-
使用类型保护:通过类型保护(Type Guard)来检查变量的类型,以确保变量在后续代码中可以安全地使用。
- typeof 类型保护:使用
typeof
操作符判断变量的类型。 - instanceof 类型保护:使用
instanceof
操作符判断变量是否是某个类的实例。 - 自定义类型保护函数:通过自定义函数来检查变量的属性、方法或其他特征,从而确定其具体类型。
类型保护可以在条件语句、函数返回值判断等场景中发挥作用,帮助开发者更好地处理不同类型的变量,避免类型错误。
- typeof 类型保护:使用
typescriptCopy code
function exampleFunction(input: string | number) {
if (typeof input === "string") {
// 在这个作用域内,TypeScript 将 input 视为字符串类型
console.log(input.toUpperCase());
} else {
// 在这个作用域内,TypeScript 将 input 视为数字类型
console.log(input.toFixed(2));
}
}
在这个例子中,我们使用了 typeof
来判断 input
的具体类型,从而在不同的作用域内安全地使用了 toUpperCase()
和 toFixed()
方法。这就是类型保护的作用。
访问接口联合类型成员
在访问联合类型的变量的属性或调用方法时,只能安全地访问那些所有组成类型共有的属性或方法:
访问接口联合类型成员与访问普通联合类型成员类似,同样需要确保访问的成员在所有接口中都存在。下面是几种方法:
- 类型断言:如果你确定变量是某个特定接口类型,你可以使用上述类型断言来告诉 TypeScript 编译器变量的具体类型。
- 使用
in
运算符:可以使用in
运算符来检查属性是否存在于接口中。 - 类型保护:通过自定义类型保护函数来缩小联合类型的范围,在确定变量的具体类型后,可以安全地访问该类型的成员。
和单独的变量未被复制不一样,当一个对象是个联合类型但未被断言和赋值时,如果调用联合类型中某个类型的函数,则会直接报错而不是输出undefined。
实例讲解
假设我们有一个应用程序,需要处理动物的声音。我们定义了两个接口,分别表示狗和猫:
interface Dog {
bark(): void;
}
interface Cat {
meow(): void;
}
现在,我们想要定义一个变量,它可以存储狗或猫的实例。我们可以使用联合类型来实现:
let pet: Dog | Cat;
接下来,我们为 pet
变量赋值。假设我们首先将其赋值为一个狗的实例:
let dog: Dog = {
bark: () => console.log("Woof!")
};
// pet.bark()会报错,因为此时pet未被断言或者赋值,且调用了其联合类型中一个类型的函数。
pet = dog;
在这里,由于我们将 dog
赋值给了 pet
,TypeScript 会根据赋值的类型推断出 pet
变量的类型为 Dog
。如果在复制前调用其中一个类型接口的函数,则会报错。
接下来,我们想要调用 pet
的 bark
方法,但由于 pet
变量的类型为 Dog | Cat
,TypeScript 编译器无法确定它是狗还是猫。这时,我们可以使用类型断言来告诉编译器,pet
是一个狗类型的实例:
(pet as Dog).bark(); // 输出: "Woof!"
现在,假设我们编写了一个函数,需要判断 pet
变量是狗还是猫。我们可以使用自定义的类型保护函数来进行判断:
function isDog(pet: Dog | Cat): pet is Dog {
return (pet as Dog).bark !== undefined;
}
if (isDog(pet)) {
pet.bark(); // 在类型保护后安全调用 bark 方法
} else {
pet.meow(); // 在类型保护后安全调用 meow 方法
}
在这个例子中,我们使用了自定义的 isDog
函数来判断 pet
是否是狗类型。该函数中用了**is
** 函数来判断类型并且。如果是狗类型,我们就可以安全地调用 bark
方法;如果不是狗类型,那么它就是猫类型,我们就可以安全地调用 meow
方法。
总结
在 TypeScript 中,联合类型(Union Types)是一种非常有用的特性,它允许一个变量具有多种可能的类型。通过联合类型,我们可以灵活地处理不同类型的值,使得代码更加健壮和灵活。
总结起来,联合类型的关键点包括:
- 定义联合类型:通过使用
|
运算符来定义多种可能的类型,例如typeA | typeB | typeC
。 - 赋值和类型推断:当一个变量的类型是联合类型时,可以根据赋值情况来推断变量的类型。如果没有赋值,变量会被默认赋值为
undefined
。 - 访问联合类型成员:当一个变量的类型是联合类型时,如果想访问某个成员(属性或方法),需要确保这个成员在所有可能的类型中都存在。否则会导致编译错误。
- 类型断言:可以使用类型断言告诉编译器一个变量的具体类型,从而绕过编译器的类型检查。
- 类型保护:通过自定义类型保护函数或使用 TypeScript 内置的
typeof
、instanceof
等操作符来缩小联合类型的范围,从而安全地访问联合类型的成员。
实践作业
假设有两个接口,分别表示矩形和圆形:
interface Rectangle {
width: number;
height: number;
}
interface Circle {
radius: number;
}
现在定义一个变量 shape
,它可以是矩形也可以是圆形,即它的类型为 Rectangle | Circle
。请完成以下任务:
- 定义一个变量
shape
,其类型为Rectangle | Circle
,但不进行任何赋值。 - 尝试调用
shape.width
,并观察 TypeScript 编译器的反应。 - 使用类型保护或类型断言来确保
shape
变量的类型,并安全地访问其成员。
作业参考答案:
typescriptCopy code
interface Rectangle {
width: number;
height: number;
}
interface Circle {
radius: number;
}
let shape: Rectangle | Circle;
// 任务 1:定义变量 shape,但不进行任何赋值
// shape;
// 任务 2:尝试调用 shape.width,并观察 TypeScript 编译器的反应
// 编译器会报错,因为它无法确定 shape 的具体类型
// 任务 3:使用类型保护或类型断言来确保 shape 变量的类型,并安全地访问其成员
function isRectangle(shape: Rectangle | Circle): shape is Rectangle {
return (shape as Rectangle).width !== undefined;
}
shape = { width: 10, height: 20 };
if (isRectangle(shape)) {
console.log("矩形的宽度为:" + shape.width);
} else {
console.log("这是一个圆形");
}
在这个作业中,我们定义了一个变量 shape
,它的类型为 Rectangle | Circle
,但没有进行任何赋值。然后尝试调用 shape.width
会导致 TypeScript 编译器报错,因为它无法确定 shape
的具体类型。最后,我们使用了类型保护来判断 shape
的类型,并安全地访问了其成员。