好的,各位观众,各位听众,欢迎来到今天的 TypeScript 类型系统深度解析课堂!我是你们的老朋友,人称“代码诗人”的编程老炮,今天咱们就来聊聊 TypeScript 类型系统里那些让人又爱又恨,又欲罢不能的宝贝们:泛型、联合类型和交叉类型。
准备好了吗? 让我们系好安全带,开启这段刺激又精彩的类型之旅吧!🚀
第一章:泛型——类型世界的变形金刚 🤖
1. 什么是泛型? 别告诉我你觉得它是个将军的名字!
在软件开发的世界里,我们经常需要编写一些通用的函数或类,它们可以处理多种类型的数据,而不需要为每种类型都写一个单独的版本。 这就像厨房里的万能调味酱,既能给牛排提味,也能给蔬菜增色。
泛型,英文名叫 Generics,就是 TypeScript 提供的这样一种“万能工具”。 它可以让我们在定义函数、接口或类的时候,预留出类型参数,等到实际使用的时候再指定具体的类型。 这样,我们就可以编写出更加灵活、可重用的代码。
举个栗子:
假设我们要写一个函数,用来获取数组的第一个元素。 如果不用泛型,我们可能需要为每种类型的数组都写一个函数:
function getNumberFirst(arr: number[]): number | undefined {
return arr[0];
}
function getStringFirst(arr: string[]): string | undefined {
return arr[0];
}
这段代码看起来是不是有点笨重? 如果我们要处理 boolean 数组、对象数组等等,岂不是要写到天荒地老? 🤯
有了泛型,一切都变得简单起来:
function getFirst<T>(arr: T[]): T | undefined {
return arr[0];
}
const numbers = [1, 2, 3];
const firstNumber = getFirst(numbers); // firstNumber 的类型是 number | undefined
const strings = ["hello", "world"];
const firstString = getFirst(strings); // firstString 的类型是 string | undefined
看到没? 我们用一个 getFirst
函数,就搞定了所有类型的数组! 其中的 <T>
就是类型参数,它可以代表任何类型。
2. 泛型的语法:尖括号里的秘密 🤫
泛型的语法很简单,就是在函数名、接口名或类名后面加上一对尖括号 <>
,并在尖括号里写上类型参数的名字。 类型参数的名字可以是任意的,通常我们用 T
(Type 的缩写)、 U
、 V
等等。
// 函数泛型
function identity<T>(arg: T): T {
return arg;
}
// 接口泛型
interface GenericInterface<T> {
value: T;
}
// 类泛型
class GenericClass<T> {
constructor(public value: T) {}
}
注意: 类型参数的作用域只在定义泛型的地方有效。 就像一个局部变量,只能在函数内部使用一样。
3. 泛型约束:给类型参数加个紧箍咒 🐒
有时候,我们希望类型参数只能是某些特定的类型,而不是任意类型。 这就像给类型参数加了一个“紧箍咒”,让它不能随便乱来。
我们可以使用 extends
关键字来约束类型参数:
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(obj: T): number {
return obj.length;
}
const str = "hello";
const strLength = logLength(str); // strLength 的类型是 number
const num = 123;
// const numLength = logLength(num); // 报错:类型“number”不满足约束“Lengthwise”
在这个例子中,我们用 extends Lengthwise
约束了类型参数 T
, 只有拥有 length
属性的类型才能作为 T
的实际类型。
4. 泛型与默认类型:贴心的备胎 😇
我们还可以为类型参数指定默认类型。 这样,在使用泛型的时候,如果没有显式地指定类型,就会使用默认类型。
function createArray<T = string>(length: number, value: T): T[] {
const arr: T[] = [];
for (let i = 0; i < length; i++) {
arr.push(value);
}
return arr;
}
const stringArray = createArray(3, "hello"); // stringArray 的类型是 string[]
const numberArray = createArray<number>(3, 123); // numberArray 的类型是 number[]
const booleanArray = createArray(3, true); // 报错:类型“boolean”不满足类型“string”的约束
在这个例子中,我们为类型参数 T
指定了默认类型 string
。 如果在使用 createArray
函数的时候没有显式地指定类型, T
就会默认为 string
类型。
5. 泛型的实际应用: 让你秒变代码大师 😎
泛型在实际开发中应用非常广泛,可以用来编写各种各样的通用函数和类。
- 通用数据结构: 比如
List
、Map
、Set
等等。 - 通用算法: 比如
sort
、filter
、map
等等。 - React 组件: 比如
Form
、Table
、Modal
等等。
掌握了泛型,你就可以像乐高积木一样,灵活地组合各种类型,构建出更加强大、可维护的代码。
第二章:联合类型——类型世界的百变星君 🎭
1. 什么是联合类型? 不是结婚登记!
联合类型,英文名叫 Union Types,允许一个变量拥有多种类型。 这就像一个百变星君,可以根据不同的场合变换成不同的形态。
联合类型使用 |
符号来表示。 例如, string | number
表示一个变量可以是字符串类型,也可以是数字类型。
举个栗子:
function printId(id: string | number) {
console.log(`ID: ${id}`);
}
printId("123"); // 输出:ID: 123
printId(456); // 输出:ID: 456
在这个例子中, printId
函数的参数 id
的类型是 string | number
, 它可以接受字符串类型的参数,也可以接受数字类型的参数。
2. 联合类型的特性:求同存异 🤝
在使用联合类型的时候,我们需要注意一些特性:
- 类型缩小 (Type Narrowing): 当我们使用联合类型的变量时,TypeScript 会根据上下文推断出变量的实际类型。 这就像侦探破案一样,通过线索一步步缩小嫌疑人的范围。
function processValue(value: string | number) {
if (typeof value === "string") {
// 在这个代码块中,value 的类型被缩小为 string
console.log(value.toUpperCase());
} else {
// 在这个代码块中,value 的类型被缩小为 number
console.log(value * 2);
}
}
- 公共属性: 如果联合类型的多个成员都有相同的属性,那么我们可以直接访问这些属性。
interface Bird {
fly(): void;
layEggs(): void;
}
interface Fish {
swim(): void;
layEggs(): void;
}
function getRandomAnimal(): Bird | Fish {
// ...
}
const animal = getRandomAnimal();
animal.layEggs(); // 可以访问 layEggs 属性,因为 Bird 和 Fish 都有 layEggs 属性
// animal.fly(); // 报错:类型“Fish”上不存在属性“fly”
3. 联合类型的实际应用: 让你代码更灵活 🤸♀️
联合类型在实际开发中应用非常广泛,可以用来处理各种各样的类型组合。
- 处理不同的输入类型: 比如一个函数可以接受字符串类型的参数,也可以接受数字类型的参数。
- 处理不同的返回值类型: 比如一个函数可能返回成功的结果,也可能返回错误的结果。
- 处理不同的状态: 比如一个组件可能处于加载中、成功、失败等不同的状态。
掌握了联合类型,你就可以编写出更加灵活、健壮的代码。
第三章:交叉类型——类型世界的合体金刚 🤖 + 🤖
1. 什么是交叉类型? 不是十字路口!
交叉类型,英文名叫 Intersection Types,可以将多个类型合并成一个类型。 这就像一个合体金刚,拥有所有组成部分的能力。
交叉类型使用 &
符号来表示。 例如, A & B
表示一个类型同时拥有 A 类型和 B 类型的属性。
举个栗子:
interface Colorful {
color: string;
}
interface Circle {
radius: number;
}
type ColorfulCircle = Colorful & Circle;
const colorfulCircle: ColorfulCircle = {
color: "red",
radius: 10,
};
在这个例子中, ColorfulCircle
类型同时拥有 Colorful
类型的 color
属性和 Circle
类型的 radius
属性。
2. 交叉类型的特性:强强联合 💪
交叉类型具有以下特性:
- 属性合并: 交叉类型会将所有组成类型的属性合并到一个类型中。 如果多个类型有相同的属性,那么最终的属性类型是这些属性类型的交叉类型。
interface A {
x: number;
}
interface B {
x: string;
y: number;
}
type C = A & B;
const c: C = {
x: "hello", // x 的类型是 number & string,也就是 never
y: 123,
};
在这个例子中, A
类型和 B
类型都有 x
属性,但是它们的类型不同。 最终 C
类型的 x
属性的类型是 number & string
, 也就是 never
类型,表示不可能同时是 number
类型和 string
类型。
- 合并后的类型必须满足所有组成类型的约束: 这就像木桶原理一样,最终类型的能力取决于最弱的组成部分。
3. 交叉类型的实际应用: 让你代码更强大 🦹
交叉类型在实际开发中应用非常广泛,可以用来组合各种各样的类型。
- 扩展现有类型: 比如为一个已有的接口添加新的属性。
- 创建混合类型: 比如创建一个同时拥有多种能力的类型。
- 实现 Mixin 模式: Mixin 是一种代码复用模式,可以将多个类的功能组合到一个类中。
掌握了交叉类型,你就可以编写出更加强大、灵活的代码。
第四章:泛型、联合类型与交叉类型的组合拳 🥊
泛型、联合类型和交叉类型可以组合使用,发挥出更大的威力。
举个栗子:
interface ApiResponse<T> {
success: boolean;
data: T | null;
error: string | null;
}
function fetchData<T>(url: string): Promise<ApiResponse<T>> {
// ...
}
interface User {
id: number;
name: string;
}
interface Product {
id: number;
name: string;
price: number;
}
async function getUser(id: number): Promise<ApiResponse<User>> {
return fetchData<User>(`/users/${id}`);
}
async function getProduct(id: number): Promise<ApiResponse<Product>> {
return fetchData<Product>(`/products/${id}`);
}
type SearchResult = User | Product;
async function search(keyword: string): Promise<ApiResponse<SearchResult[]>> {
return fetchData<SearchResult[]>(`/search?keyword=${keyword}`);
}
在这个例子中,我们使用了泛型、联合类型和交叉类型来定义一个通用的 API 响应类型 ApiResponse
, 以及用于获取用户、产品和搜索结果的函数。
ApiResponse<T>
使用泛型来表示响应数据的类型。data: T | null
使用联合类型来表示响应数据可以是T
类型,也可以是null
。SearchResult = User | Product
使用联合类型来表示搜索结果可以是User
类型,也可以是Product
类型。
通过组合使用泛型、联合类型和交叉类型,我们可以编写出更加灵活、可重用的代码。
总结:成为类型大师的秘诀 🧙♂️
今天,我们一起深入探讨了 TypeScript 类型系统中的三大法宝:泛型、联合类型和交叉类型。 它们就像三位武林高手,各有千秋,又可以互相配合,发挥出强大的力量。
想要成为类型大师,你需要:
- 理解它们的定义和特性: 就像了解每个招式的基本功一样。
- 掌握它们的语法: 就像熟练掌握武器的使用方法一样。
- 灵活运用它们解决实际问题: 就像在实战中运用所学武功一样。
记住,学习 TypeScript 类型系统是一个持续不断的过程。 多练习、多思考、多总结,你终将成为一名真正的类型大师!💪
希望今天的课程对你有所帮助。 感谢大家的观看,我们下节课再见! 👋