TypeScript 泛型和鸭子类型详解

摘要:TypeScript 有两个很重要的特性:泛型和鸭子类型。它们让代码更灵活、更安全。下面我们来详细了解这些概念。

TypeScript 有两个很重要的特性:泛型和鸭子类型。它们让代码更灵活、更安全。下面我们来详细了解这些概念。


泛型:让代码更通用

泛型就像是一个模板,可以让我们写出适用于多种类型的代码。

泛型函数

泛型函数允许我们在调用时指定具体类型。

function identity<T>(arg: T): T {
    return arg;
}

let result = identity<number>(42);
// result 的类型是 number

这里的 T 是类型参数,在调用函数时确定具体类型。

泛型接口

接口也可以使用泛型。

interface Container<T> {
    value: T;
}

let container: Container<number> = { value: 100 };

泛型类

类同样支持泛型。

class Stack<T> {
    private items: T[] = [];
    
    push(item: T): void {
        this.items.push(item);
    }
    
    pop(): T | undefined {
        return this.items.pop();
    }
}

let stack = new Stack<number>();
stack.push(1);
stack.push(2);
let item = stack.pop(); // item 的类型是 number | undefined


类型操作技巧

条件类型

根据条件决定使用什么类型。

type Check<T> = T extends string ? true : false;
type Result = Check<'hello'>; // Result 的类型是 true

keyof 操作符

获取类型的所有属性名。

interface Person {
    name: string;
    age: number;
}

type PersonKeys = keyof Person; // "name" | "age"
type PersonNameType = Person['name']; // string

类型推断

从其他类型中提取信息。

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function add(a: number, b: number): number {
    return a + b;
}

type AddReturn = ReturnType<typeof add>; // number

类型约束

限制泛型参数必须满足的条件。

interface HasName {
    name: string;
}

function printName<T extends HasName>(obj: T): void {
    console.log(obj.name);
}

printName({ name: 'John', age: 25 }); // 输出 'John'


常用的内置工具类型

TypeScript 提供了一些实用的工具类型。

Partial
让所有属性变成可选的。

interface Person {
    name: string;
    age: number;
}

type PartialPerson = Partial<Person>;
const partialPerson: PartialPerson = { name: 'John' }; // age 是可选的

Required
让所有属性变成必需的。

interface Person {
    name?: string;
    age?: number;
}

type RequiredPerson = Required<Person>;
const requiredPerson: RequiredPerson = { name: 'John', age: 25 };

Pick
从类型中挑选部分属性。

interface Person {
    name: string;
    age: number;
    address: string;
}

type NameAndAge = Pick<Person, 'name' | 'age'>;
const person: NameAndAge = { name: 'John', age: 25 };

Omit
从类型中排除某些属性。

interface Person {
    name: string;
    age: number;
    address: string;
}

type PersonWithoutAddress = Omit<Person, 'address'>;

Readonly
让所有属性变成只读的。

interface Person {
    name: string;
    age: number;
}

type ReadonlyPerson = Readonly<Person>;
const readonlyPerson: ReadonlyPerson = { name: 'John', age: 25 };
// readonlyPerson.name = 'Bob'  // 错误:不能修改只读属性


鸭子类型:关注行为而非名称

鸭子类型是 TypeScript 的一个重要特性。它的理念是:如果一个对象走起来像鸭子,叫起来像鸭子,那么它就是鸭子。

实际例子

interface Duck {
    walk: () => void;
    quack: () => void;
}

function doDuckThings(duck: Duck) {
    duck.walk();
    duck.quack();
}

任何具有 walk 和 quack 方法的对象都可以被当作 Duck 使用:

const myDuck = {
    walk: () => console.log('走路...'),
    quack: () => console.log('叫声...'),
    swim: () => console.log('游泳...') // 额外的方法也没问题
};

doDuckThings(myDuck); // 正常工作

即使 myDuck 没有明确声明实现 Duck 接口,只要它有需要的属性和方法,就可以使用。


鸭子类型的优势

灵活性
代码更容易适应变化。只要结构匹配,就可以使用。

function printToString(obj: { toString: () => string }) {
    console.log(obj.toString());
}

printToString(123); // 数字有 toString 方法
printToString(new Date()); // 日期对象也有 toString 方法
printToString({ toString: () => '自定义对象' }); // 任何有 toString 的对象都可以

代码复用
相同的函数可以用于多种不同类型的对象。

interface Saveable {
    save: () => void;
}

function saveItem(item: Saveable) {
    item.save();
}

class User {
    save() {
        console.log('保存用户');
    }
}

class Product {
    save() {
        console.log('保存产品');
    }
}

saveItem(new User()); // 可以
saveItem(new Product()); // 也可以


实际应用建议

什么时候使用泛型

  • 需要创建可重用的组件时

  • 函数需要处理多种类型时

  • 想要保持类型安全的同时提供灵活性

什么时候依赖鸭子类型

  • 编写测试时(容易创建模拟对象)

  • 设计插件系统时

  • 需要松耦合的架构时

注意事项

  • 虽然鸭子类型很灵活,但有时候需要更严格的类型检查

  • 复杂的泛型可能让代码难以理解

  • 在团队项目中要保持一致的编码规范


总结

泛型和鸭子类型让 TypeScript 既强大又灵活。泛型提供类型安全性,鸭子类型提供灵活性。掌握这些概念后,你可以写出更通用、更安全的代码。

在实际项目中,建议先从简单的泛型开始,逐步学习更复杂的类型操作。鸭子类型则可以帮助你设计出更松耦合、更易测试的代码结构。

记住,这些特性都是为了帮助你写出更好的代码。不要为了使用而使用,要根据实际需求选择合适的技术方案。

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://shenqiku.cn/article/FLY_13167