泛型:代码的"变色龙" 🦎
为什么需要泛型?
想象一个快递员,每天要处理不同类型的包裹。如果为每种类型的包裹都写一套处理程序,那会很麻烦。泛型就像是一个"通用的包裹处理系统",它能智能地适应不同类型的包裹。
让我们通过一个简单的例子来理解:
// 没有泛型时,我们需要为每种类型写一个函数
function processNumberBox(box: { value: number }) {
console.log(box.value + 10);
}
function processStringBox(box: { value: string }) {
console.log(box.value.toUpperCase());
}
// 使用泛型后,一个函数就能处理所有类型
function processBox<T>(box: { value: T }) {
console.log(box.value);
return box.value;
}
// 现在可以处理任何类型
processBox({ value: 42 }); // 处理数字
processBox({ value: "hello" }); // 处理字符串
processBox({ value: true }); // 处理布尔值
泛型就像"变量的变量" 📦
// 创建一个通用的"盒子"类
class Box<T> {
private content: T;
constructor(value: T) {
this.content = value;
}
getValue(): T {
return this.content;
}
}
// 创建不同类型的盒子
const numberBox = new Box(123); // 数字盒子
const stringBox = new Box("hello"); // 字符串盒子
const dateBox = new Box(new Date()); // 日期盒子
// TypeScript 会自动推断并确保类型安全
numberBox.getValue().toFixed(2); // ✅ 可以,因为是数字
stringBox.getValue().toUpperCase(); // ✅ 可以,因为是字符串
实际应用场景 🎯
- 创建通用数据结构
// 创建一个通用的"盒子"类
class Box<T> {
private content: T;
constructor(value: T) {
this.content = value;
}
getValue(): T {
return this.content;
}
}
// 创建不同类型的盒子
const numberBox = new Box(123); // 数字盒子
const stringBox = new Box("hello"); // 字符串盒子
const dateBox = new Box(new Date()); // 日期盒子
// TypeScript 会自动推断并确保类型安全
numberBox.getValue().toFixed(2); // ✅ 可以,因为是数字
stringBox.getValue().toUpperCase(); // ✅ 可以,因为是字符串
numberBox.getValue().toUpperCase(); // ❌ 错误!数字没有toUpperCase方法
- API响应处理
// 通用的API响应处理器
interface ApiResponse<T> {
success: boolean;
data: T;
message: string;
}
// 用户接口
interface User {
id: number;
name: string;
}
// 使用泛型处理不同类型的响应
async function fetchUser(): Promise<ApiResponse<User>> {
// ... API调用
return {
success: true,
data: { id: 1, name: "张三" },
message: "获取成功"
};
}
类型守卫:代码的"安检员" 🚨
什么是类型守卫?
想象你是一个夜店保安,需要检查进入的人是否满足条件(年龄、着装等)。类型守卫就是代码世界的"保安",它帮助我们在运行时确保数据类型的正确性。
// 定义可能的类型
type Animal = Bird | Dog;
interface Bird {
type: "bird";
flySpeed: number;
}
interface Dog {
type: "dog";
runSpeed: number;
}
// 类型守卫函数
function isBird(animal: Animal): animal is Bird {
return animal.type === "bird";
}
// 使用类型守卫
function moveAnimal(animal: Animal) {
if (isBird(animal)) {
// TypeScript现在知道这是一只鸟
console.log(`飞行速度:${animal.flySpeed}`);
} else {
// TypeScript知道这是一只狗
console.log(`奔跑速度:${animal.runSpeed}`);
}
}
常见的类型守卫方式
- typeof 守卫
function process(value: string | number) {
if (typeof value === "string") {
// 这里TypeScript知道value是字符串
return value.toUpperCase();
} else {
// 这里TypeScript知道value是数字
return value.toFixed(2);
}
}
- instanceof 守卫
class Car {
drive() { console.log("开车"); }
}
class Bicycle {
ride() { console.log("骑行"); }
}
function useVehicle(vehicle: Car | Bicycle) {
if (vehicle instanceof Car) {
vehicle.drive();
} else {
vehicle.ride();
}
}
实用工具类型:代码的"瑞士军刀" 🛠️
TypeScript的工具类型也是这样的一个好用的工具集合:
1. Partial<T> - 所有属性变为可选
interface User {
name: string;
age: number;
email: string;
}
// 所有字段都变成可选的
type PartialUser = Partial<User>;
// 等同于:
// {
// name?: string;
// age?: number;
// email?: string;
// }
// 实际应用:更新用户信息
function updateUser(userId: number, updates: Partial<User>) {
// 可以只更新部分字段
}
2. Required<T> - 所有属性变为必需
interface Config {
name?: string;
timeout?: number;
}
// 所有字段都变成必需的
type RequiredConfig = Required<Config>;
// 等同于:
// {
// name: string;
// timeout: number;
// }
实际应用:创建表单验证规则
type ValidationRules<T> = {
[K in keyof T]: {
required?: boolean;
minLength?: number;
maxLength?: number;
pattern?: RegExp;
}
}
// 使用
const employeeValidation: ValidationRules<Employee> = {
name: { required: true, minLength: 2 },
salary: { required: true, pattern: /^\d+$/ },
department: { required: true }
};
映射类型:批量"加工"属性 🏭
想象你有一个印章,可以给所有属性都盖上"只读"的标记:
// 原始接口
interface Employee {
name: string;
salary: number;
department: string;
}
// 让所有属性只读
type ReadonlyEmployee = {
readonly [K in keyof Employee]: Employee[K];
}
// 让所有属性可选
type OptionalEmployee = {
[K in keyof Employee]?: Employee[K];
}
这些高级类型特性看起来可能有点复杂,但它们就像乐高积木一样 - 一旦你理解了基本概念,就可以把它们组合起来构建更复杂的结构。这些特性的目的是帮助我们写出更安全、更可维护的代码。