05.TypeScript 类型断言 、联合类型 、 交叉类型
1. 类型断言
类型断言(Type Assertion)可以用来手动指定一个值的类型。
1.1 语法
类型断言有两种形式:
- "尖括号"语法:
<类型>值; - as 语法:
值 as 类型。
1.2 实例
// 尖括号语法
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
// 我们明确告诉编译器 someValue 是一个字符串类型,因此可以访问其 length 属性
console.log(strLength); // 输出: 16
// as 语法
let someValue2: any = "this is a string";
let strLength2: number = (someValue2 as string).length;
// 使用 as 语法同样明确告诉编译器 someValue2 是一个字符串类型
console.log(strLength2); // 输出: 16
1.3 非空断言操作符(!)
在上下文中当类型检查器无法断定类型时,可以使用 ! 进行断言操作符来手动指定其类型不为 null 和 undefined。
function getLength(s: string | null): number {
return s!.length; // 断言 s 不为 null
}
// 我们明确告诉编译器 s 不会是 null,因此可以直接访问其 length 属性
console.log(getLength("hello")); // 输出: 5
// 如果传入 null,运行时会报错
// console.log(getLength(null)); // 运行时错误
2. 联合类型
联合类型(Union Types)表示取值可以为多种类型中的一种。
2.1 实例
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
// myFavoriteNumber 可以是字符串或数字类型,因此两种赋值都有效
console.log(myFavoriteNumber); // 输出: 7
// 当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,
// 我们只能访问此联合类型的所有类型里共有的属性或方法
function getLength2(something: string | number): number {
// return something.length; // 错误: number 类型没有 length 属性
return something.toString().length; // 正确: toString() 是 string 和 number 的共有方法
}
// 由于 number 类型没有 length 属性,我们需要先转换为字符串再获取长度
console.log(getLength2("hello")); // 输出: 5
console.log(getLength2(123)); // 输出: 3
3. 交叉类型
交叉类型(Intersection Types)将多个类型合并为一个类型,包含了所需的所有类型的特性。
3.1 实例
interface IAnyObject {
[propName: string]: any;
}
interface Bird {
fly(): void;
layEggs(): void;
}
interface Fish {
swim(): void;
layEggs(): void;
}
// 交叉类型
const pet: Bird & Fish = {
fly() {
console.log('Bird is flying');
},
swim() {
console.log('Fish is swimming');
},
layEggs() {
console.log('Laying eggs');
}
};
// pet 对象同时具有 Bird 和 Fish 接口的所有方法
pet.fly(); // 输出: Bird is flying
pet.swim(); // 输出: Fish is swimming
pet.layEggs(); // 输出: Laying eggs
4. 类型守卫
类型守卫可以帮助我们在运行时检查变量的类型。
4.1 typeof 类型守卫
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return " ".repeat(padding) + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
// 当 padding 是数字时,我们重复空格字符串
console.log(padLeft("Hello world", 4)); // 输出: " Hello world"
// 当 padding 是字符串时,我们直接拼接字符串
console.log(padLeft("Hello world", "---")); // 输出: "---Hello world"
4.2 instanceof 类型守卫
class Bird {
fly() {
console.log("Bird is flying");
}
layEggs() {
console.log("Laying eggs");
}
}
class Fish {
swim() {
console.log("Fish is swimming");
}
layEggs() {
console.log("Laying eggs");
}
}
function move(animal: Bird | Fish) {
if (animal instanceof Bird) {
// 通过 instanceof 确定 animal 是 Bird 类型,可以调用 fly 方法
animal.fly(); // 输出: Bird is flying
} else if (animal instanceof Fish) {
// 通过 instanceof 确定 animal 是 Fish 类型,可以调用 swim 方法
animal.swim(); // 输出: Fish is swimming
}
}
move(new Bird()); // 输出: Bird is flying
move(new Fish()); // 输出: Fish is swimming
5. 类型别名
类型别名用来给一个类型起个新名字。
5.1 实例
// 基本类型别名
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n;
} else {
return n();
}
}
// 传入字符串时直接返回
console.log(getName("Alice")); // 输出: Alice
// 传入函数时调用函数返回结果
console.log(getName(() => "Bob")); // 输出: Bob
// 字符串字面量类型别名
type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
// 处理事件
console.log(`Handling ${event} event`);
}
// handleEvent(document.getElementById('hello'), 'click'); // 正确
// handleEvent(document.getElementById('hello'), 'dbclick'); // 错误: dbclick 不是 EventNames 类型
// EventNames 限制了 event 参数只能是 'click'、'scroll' 或 'mousemove' 中的一个
handleEvent(document.getElementById('hello')!, 'click'); // 输出: Handling click event
6. 总结
- 类型断言允许我们手动指定值的类型,帮助编译器理解我们的意图;
- 联合类型表示取值可以为多种类型中的一种,增强了类型的灵活性;
- 交叉类型将多个类型合并为一个类型,包含了所需的所有类型的特性;
- 类型守卫帮助在运行时检查变量的类型,提高代码的安全性;
- 类型别名为类型起新名字,提高代码可读性和维护性。