一、泛型的基本概念
泛型就像是一个"类型占位符",它允许我们在定义函数、类或接口时暂不指定具体类型,等到使用时再确定具体类型。这就像做月饼的模具,模具的形状固定(泛型的约束),但可以填充不同口味的馅料(具体类型)。
在你这段代码中的 <T>
就是泛型参数,读作"类型T"。
二、为什么要用泛型?
观察你的函数定义:
typescript
function echoWithLength<T extends IWithLength>(arg: T): T {
console.log(arg.length);
return arg;
}
如果不用泛型,代码可能会写成:
typescript
function echoWithLength(arg: IWithLength): IWithLength {
console.log(arg.length);
return arg;
}
这两种写法的关键区别在于类型信息的保留:
调用示例 | 泛型版本返回类型 | 非泛型版本返回类型 |
---|---|---|
echoWithLength("hello") |
string |
IWithLength |
echoWithLength([1,2,3]) |
number[] |
IWithLength |
使用泛型时,返回类型会与输入类型完全一致,保留了更多类型信息,这是泛型的核心优势。
三、extends
的作用:类型约束
<T extends IWithLength>
的语法表示: "类型T必须是实现了IWithLength接口的类型,即必须有length: number
属性"
这就像给泛型T加上了一个安全限制:
typescript
interface IWithLength {
length: number; // 必须满足这个条件
}
实际效果: ✅ 允许的类型:string
, any[]
, {length: number}
❌ 不允许的类型:number
, boolean
, {}
(没有length属性的对象)
四、你的代码执行过程分析
1. 字符串调用
typescript
const str1 = echoWithLength('arr');
T
被推断为string
类型- 字符串天然具有
length
属性 - 返回类型仍是
string
2. 对象调用
typescript
const obj = echoWithLength({ length: 10 });
T
被推断为{ length: number }
- 显式提供了 length 属性
- 返回类型保持为
{ length: number }
3. 数组调用
typescript
const arr2 = echoWithLength([1,2,3]);
T
被推断为number[]
- 数组天然具有
length
属性 - 返回类型仍是
number[]
五、类型推断的智能表现
TypeScript 编译器会自动进行类型推断,你甚至可以不显式指定泛型:
typescript
// 编译器会自动推断 T 为 string
echoWithLength("hello");
// 等同于显式声明
echoWithLength<string>("hello");
六、为什么不能直接用 any?
如果代码写成:
typescript
function echoWithLength(arg: any): any {
console.log(arg.length);
return arg;
}
这样会失去:
- 参数的类型检查
- 返回值的类型关联
- 自动补全等IDE支持
七、实际应用场景
这种模式常见于需要保持输入输出类型一致,同时需要访问某些公共属性的场景:
- 处理集合(数组、字符串等)的工具函数
- 需要 length 属性的数据验证
- React组件 props 的类型约束
八、学习建议
要深入理解泛型,推荐尝试以下练习:
- 写一个返回数组最后一个元素的函数
- 实现一个支持多种类型的键值对存储类
- 创建一个能合并两个对象并保留类型信息的函数
简单说一下ts中的泛型,它是一个类型的占位符,可以用在函数类接口中的定义当中,可以先不指定具体类型,等到时候使用时才确定具体类型。