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 的类型是 truekeyof 操作符
获取类型的所有属性名。
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 既强大又灵活。泛型提供类型安全性,鸭子类型提供灵活性。掌握这些概念后,你可以写出更通用、更安全的代码。
在实际项目中,建议先从简单的泛型开始,逐步学习更复杂的类型操作。鸭子类型则可以帮助你设计出更松耦合、更易测试的代码结构。
记住,这些特性都是为了帮助你写出更好的代码。不要为了使用而使用,要根据实际需求选择合适的技术方案。
本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!