前端开发入门指南Day 17:TypeScript高级类型(泛型,类型守卫,Partial<T>和 Required<T>等)

发布于:2024-12-08 ⋅ 阅读:(111) ⋅ 点赞:(0)

泛型:代码的"变色龙" 🦎

为什么需要泛型?

想象一个快递员,每天要处理不同类型的包裹。如果为每种类型的包裹都写一套处理程序,那会很麻烦。泛型就像是一个"通用的包裹处理系统",它能智能地适应不同类型的包裹。

让我们通过一个简单的例子来理解:

// 没有泛型时,我们需要为每种类型写一个函数
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(); // ✅ 可以,因为是字符串

实际应用场景 🎯

  1. 创建通用数据结构
// 创建一个通用的"盒子"类
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方法
  1. 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}`);
    }
}

常见的类型守卫方式

  1. typeof 守卫
function process(value: string | number) {
    if (typeof value === "string") {
        // 这里TypeScript知道value是字符串
        return value.toUpperCase();
    } else {
        // 这里TypeScript知道value是数字
        return value.toFixed(2);
    }
}
  1. 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];
}

这些高级类型特性看起来可能有点复杂,但它们就像乐高积木一样 - 一旦你理解了基本概念,就可以把它们组合起来构建更复杂的结构。这些特性的目的是帮助我们写出更安全、更可维护的代码。