TypeScript 泛型和鸭子类型详解

更新日期: 2025-11-12 阅读: 26 标签: 泛型

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://fly63.com/article/detial/13167

TypeScript 泛型的通俗解释

在 TypeScript 中我们会使用泛型来对函数的相关类型进行约束。这里的函数,同时包含 class 的构造函数,因此,一个类的声明部分,也可以使用泛型。那么,究竟什么是泛型?如果通俗的理解泛型呢?

泛型,很多人因它放弃学习 TypeScript?

Java是和typescript一样支持泛型的,当我在大学开始学习Java的时候,我还是一个菜鸟码农,遇到难点(比如泛型)就直接跳过,能学多少学多少,回寝室就LOL开黑。直到大学毕业我依旧没有理解泛型的概念

TypeScript泛型参数默认类型 和 新的 --strict 编译选项

TypeScript 2.3 增加了对声明泛型参数默认类型的支持,允许为泛型类型中的类型参数指定默认类型。接下来看看如何通过泛型参数默认将以下React组件从 JS (和JSX)迁移到 TypeScript (和TSX):

这些高阶ts内置泛型帮助类型,你用过几个

本文将简要介绍一些工具泛型使用及其实现, 这些泛型接口定义大多数是语法糖(简写), 你可以在 typescript 包中的 lib.es5.d.ts 中找到它的定义, 我们项目的版本

TypeScript的索引类型与映射类型,以及常用工具泛型的实现

相信现在很多小伙伴都在使用 TypeScript(以下简称 TS),在 TS 中除了一些常用的基本类型外,还有一些稍微高级一点的类型,这些就是我本次文章要讲的内容

一文读懂 TypeScript 泛型及应用

泛型是静态类型语言的基本特征,允许将类型作为参数传递给另一个类型、函数、或者其他结构。TypeScript 支持泛型作为将类型安全引入组件的一种方式。

秒懂 TypeScript 泛型工具类型!

如果你刚接触 TypeScript 不久,在阅读 TypeScript 内置工具类型的用法和内部实现的文章时,可能会看到 Pick 工具类型,对于该类型的语法你可能会感到陌生。

如何在 TypeScript 中使用泛型

泛型是静态类型语言的一个基本特征,允许开发人员将类型作为参数传递给另一个类型、函数或其他结构。当开发人员使他们的组件成为通用组件时,他们赋予该组件接受和强制执行在使用该组件时传入的类型的能力

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!