08.TypeScript 类型推论、类型别名、泛型
1. 类型推论
TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。
1.1 基础类型推论
// 变量声明时进行类型推论
let x = 3; // 推论为 number 类型
let str = "hello"; // 推论为 string 类型
let isDone = false; // 推论为 boolean 类型
// 函数返回值的类型推论
function add(a: number, b: number) {
return a + b; // 推论返回值为 number 类型
}
console.log("基础类型推论结果:");
console.log(typeof x); // 输出: number
console.log(typeof str); // 输出: string
console.log(typeof isDone); // 输出: boolean
console.log("add函数返回值类型:", typeof add(1, 2)); // 输出: number
1.2 输出结果
1.3 联合类型推论
当需要从几个表达式中推断类型时候,会使用这些表达式的类型来推断出一个最合适的通用类型。
let arr = [0, 1, null]; // 推论为 (number | null)[]
let mixed = [1, "hello", true]; // 推论为 (string | number | boolean)[]
console.log("联合类型推论结果:");
console.log(arr); // 输出: [0, 1, null]
console.log(mixed); // 输出: [1, "hello", true]
1.4 类型推论结果
1.5 上下文类型推论
TypeScript 类型推论也可能按照相反的方向进行,这被称为"上下文类型"。
// 上下文类型推论
window.onmousedown = function(mouseEvent) {
console.log(mouseEvent.button); // mouseEvent 被推论为 MouseEvent 类型
console.log(mouseEvent.clientX); // 可以访问 MouseEvent 的属性
};
// 另一个上下文类型推论的例子
let handler: (e: KeyboardEvent) => void;
handler = function(event) {
console.log(event.key); // event 被推论为 KeyboardEvent 类型
};
console.log("上下文类型推论示例:");
console.log("window.onmousedown 已设置");
console.log("handler 函数已定义");
1.6 推论结果
1.7 总结
- 类型推论是 TypeScript 在没有明确指定类型时自动推断类型的能力;
- 基础类型推论会根据初始值自动推断变量类型;
- 联合类型推论会从多个可能的类型中选择最合适的通用类型;
- 上下文类型推论会根据函数所处的上下文环境推断参数类型;
- 类型推论可以减少代码中的类型注解,使代码更简洁。
2. 类型别名
类型别名用来给一个类型起个新名字,使用 type 关键字来创建。
2.1 基本类型别名
// 基本类型别名
type Name = string;
type Age = number;
type IsActive = boolean;
let userName: Name = "张三";
let userAge: Age = 25;
let isActive: IsActive = true;
console.log("基本类型别名结果:");
console.log("用户名:", userName); // 输出: 张三
console.log("年龄:", userAge); // 输出: 25
console.log("是否激活:", isActive); // 输出: true
2.2 类型展示
2.2 联合类型别名
// 联合类型别名
type StringOrNumber = string | number;
type Status = "success" | "error" | "pending";
let id: StringOrNumber = 123;
let status: Status = "success";
console.log("联合类型别名结果:");
console.log("ID:", id); // 输出: 123
console.log("状态:", status); // 输出: success
// 切换类型
id = "ABC123";
status = "error";
console.log("切换后的ID:", id); // 输出: ABC123
console.log("切换后的状态:", status); // 输出: error
2.3 类型结果
2.4 对象类型别名
// 对象类型别名
type User = {
name: string;
age: number;
email?: string; // 可选属性
};
type Point = {
x: number;
y: number;
};
let user: User = {
name: "李四",
age: 30
};
let point: Point = {
x: 10,
y: 20
};
console.log("对象类型别名结果:");
console.log("用户:", user); // 输出: { name: '李四', age: 30 }
console.log("坐标点:", point); // 输出: { x: 10, y: 20 }
2.5 输出结果
2.6 函数类型别名
// 函数类型别名
type MathOperation = (a: number, b: number) => number;
type Callback = (data: any) => void;
let add: MathOperation = function(x, y) {
return x + y;
};
let logCallback: Callback = function(data) {
console.log("回调数据:", data);
};
console.log("函数类型别名结果:");
console.log("加法结果:", add(5, 3)); // 输出: 8
logCallback("测试数据"); // 输出: 回调数据: 测试数据
2.7 类型结果
2.8 泛型类型别名
// 泛型类型别名
type Container<T> = {
value: T;
getValue: () => T;
};
type Pair<T, U> = {
first: T;
second: U;
};
let numberContainer: Container<number> = {
value: 42,
getValue: function() { return this.value; }
};
let stringPair: Pair<string, number> = {
first: "hello",
second: 100
};
console.log("泛型类型别名结果:");
console.log("容器值:", numberContainer.getValue()); // 输出: 42
console.log("配对值:", stringPair); // 输出: { first: 'hello', second: 100 }
2.9 类型结果
2.10 交叉类型别名
// 交叉类型别名
type Person = {
name: string;
age: number;
};
type Employee = {
id: number;
department: string;
};
type EmployeePerson = Person & Employee;
let staff: EmployeePerson = {
name: "王五",
age: 28,
id: 1001,
department: "技术部"
};
console.log("交叉类型别名结果:");
console.log("员工信息:", staff); // 输出: { name: '王五', age: 28, id: 1001, department: '技术部' }
2.11 类型结果
2.12 类型别名与接口的区别
// 类型别名可以表示原始类型、联合类型、元组等
// 接口主要用于定义对象类型
type StringOrNumber = string | number; // 类型别名可以表示联合类型
interface Person {
name: string;
age: number;
} // 接口主要用于定义对象类型
// 类型别名不能被继承或实现
// 接口可以被类实现或被其他接口继承
let value: StringOrNumber = "test";
let person: Person = { name: "赵六", age: 35 };
console.log("类型别名与接口区别结果:");
console.log("值:", value); // 输出: test
console.log("人:", person); // 输出: { name: '赵六', age: 35 }
2.13 类型结果
2.14 总结
- 类型别名使用
type关键字创建,可以给任何类型起别名; - 类型别名可以表示原始类型、联合类型、交叉类型、元组等复杂类型;
- 类型别名可以提高代码的可读性和可维护性;
- 类型别名支持泛型,可以创建可重用的类型定义;
- 类型别名与接口的主要区别在于:类型别名更灵活,可以表示各种类型,而接口主要用于定义对象类型;
- 接口可以被类实现和继承,而类型别名不能;
- 在定义对象类型时,接口通常是更好的选择,而在定义联合类型、交叉类型等复杂类型时,类型别名更合适。
3. 泛型
泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。泛型可以创建可重用的组件,一个组件可以支持多种类型的数据。
3.1 泛型函数
// 泛型函数
function identity<T>(arg: T): T {
return arg;
}
// 使用泛型函数
let output1 = identity<string>("hello"); // 指定类型参数
let output2 = identity("world"); // 类型推论
console.log("泛型函数结果:");
console.log(output1); // 输出: hello
console.log(output2); // 输出: world
3.2 泛型结果
3.3 泛型接口
// 泛型接口
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity2<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity2;
console.log("泛型接口结果:");
console.log(myIdentity(123)); // 输出: 123
3.4 泛型结果
3.5 泛型约束
// 泛型约束
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // 现在我们知道 arg 有 length 属性
return arg;
}
console.log("泛型约束结果:");
console.log(loggingIdentity("hello")); // 输出: hello
console.log(loggingIdentity([1, 2, 3])); // 输出: [1, 2, 3]
// loggingIdentity(3); // 错误,number 没有 length 属性
3.6 泛型结果
3.7 在泛型约束中使用类型参数
// 在泛型约束中使用类型参数
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
console.log("泛型约束中使用类型参数结果:");
console.log(getProperty(x, "a")); // 输出: 1
console.log(getProperty(x, "d")); // 输出: 4
3.8 输出结果
3.9 总结
- 泛型允许在定义函数、接口或类时不预先指定具体类型,而在使用时再指定;
- 泛型可以创建可重用的组件,一个组件可以支持多种类型的数据;
- 泛型函数可以在调用时显式指定类型参数,也可以依赖类型推论;
- 泛型接口可以定义泛型函数的形状;
- 泛型类可以确保类的属性和方法在使用时具有正确的类型;
- 泛型约束通过
extends关键字实现,可以限制泛型参数的类型; - 在泛型约束中可以使用类型参数,通过
keyof操作符获取对象的键类型。
4. 总结
- 类型推论是 TypeScript 的核心特性之一,可以在没有明确类型注解的情况下自动推断类型;
- 类型推论包括基础类型推论、最佳通用类型推论和上下文类型推论三种方式;
- 类型别名使用
type关键字创建,可以给任何类型起别名,提高代码可读性; - 类型别名支持各种复杂类型,包括联合类型、交叉类型、泛型类型等;
- 类型别名与接口各有适用场景:接口适合定义对象类型,类型别名适合定义复杂类型;
- 泛型允许在定义函数、接口或类时不预先指定具体类型,而在使用时再指定;
- 泛型可以创建可重用的组件,一个组件可以支持多种类型的数据;
- 合理使用类型推论、类型别名和泛型可以使 TypeScript 代码更加简洁、可读和可维护;
- 类型推论减少了冗余的类型注解,类型别名提供了更灵活的类型定义方式,泛型增强了代码的可重用性。