08.TypeScript 类型推论、类型别名、泛型

69 阅读7分钟

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 总结

  1. 类型推论是 TypeScript 在没有明确指定类型时自动推断类型的能力;
  2. 基础类型推论会根据初始值自动推断变量类型;
  3. 联合类型推论会从多个可能的类型中选择最合适的通用类型;
  4. 上下文类型推论会根据函数所处的上下文环境推断参数类型;
  5. 类型推论可以减少代码中的类型注解,使代码更简洁。

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 总结

  1. 类型别名使用 type 关键字创建,可以给任何类型起别名;
  2. 类型别名可以表示原始类型、联合类型、交叉类型、元组等复杂类型;
  3. 类型别名可以提高代码的可读性和可维护性;
  4. 类型别名支持泛型,可以创建可重用的类型定义;
  5. 类型别名与接口的主要区别在于:类型别名更灵活,可以表示各种类型,而接口主要用于定义对象类型;
  6. 接口可以被类实现和继承,而类型别名不能;
  7. 在定义对象类型时,接口通常是更好的选择,而在定义联合类型、交叉类型等复杂类型时,类型别名更合适。

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 总结

  1. 泛型允许在定义函数、接口或类时不预先指定具体类型,而在使用时再指定;
  2. 泛型可以创建可重用的组件,一个组件可以支持多种类型的数据;
  3. 泛型函数可以在调用时显式指定类型参数,也可以依赖类型推论;
  4. 泛型接口可以定义泛型函数的形状;
  5. 泛型类可以确保类的属性和方法在使用时具有正确的类型;
  6. 泛型约束通过 extends 关键字实现,可以限制泛型参数的类型;
  7. 在泛型约束中可以使用类型参数,通过 keyof 操作符获取对象的键类型。

4. 总结

  1. 类型推论是 TypeScript 的核心特性之一,可以在没有明确类型注解的情况下自动推断类型;
  2. 类型推论包括基础类型推论、最佳通用类型推论和上下文类型推论三种方式;
  3. 类型别名使用 type 关键字创建,可以给任何类型起别名,提高代码可读性;
  4. 类型别名支持各种复杂类型,包括联合类型、交叉类型、泛型类型等;
  5. 类型别名与接口各有适用场景:接口适合定义对象类型,类型别名适合定义复杂类型;
  6. 泛型允许在定义函数、接口或类时不预先指定具体类型,而在使用时再指定;
  7. 泛型可以创建可重用的组件,一个组件可以支持多种类型的数据;
  8. 合理使用类型推论、类型别名和泛型可以使 TypeScript 代码更加简洁、可读和可维护;
  9. 类型推论减少了冗余的类型注解,类型别名提供了更灵活的类型定义方式,泛型增强了代码的可重用性。