内容提要
TypeScript 泛型可将类型作为参数传入,支持类型推断与 extends 约束。它不仅能复用类型,还能绑定输入输出类型;结合映射类型可精确推导对象键与函数参数,解决包装函数中的类型关联问题,避免使用 any。
延伸解读
泛型不只是复用:类型联动才是关键
文章指出,泛型除了复用类型,还能绑定输入与输出类型。例如 identity 函数中,Type 同时约束参数和返回值,使两者类型一致。getProperty 示例进一步展示:Key extends keyof Type 将键参数绑定到对象类型,传入不存在的键会报错。这种联动能力让包装函数能精确推导参数与返回值,避免类型丢失。
extends 约束:为泛型划定安全边界
默认泛型可以是任意类型,这会导致无法访问特定属性。文章通过 Lengthwise 接口示例说明,使用 <T extends Lengthwise> 可要求 T 必须具有 length 属性,从而安全调用 arg.length。任何拥有 length 的类型(如数组)都满足约束。这提高了代码可读性,并在编译期拦截不合规的类型。
映射类型与 keyof:精确推导对象键与函数参数
文章用 myMap 示例展示如何结合 keyof 和映射类型,为包装后的对象生成精确类型。wrapper 函数利用 K extends keyof typeof myMap 和 Parameters<typeof fn>,使返回的异步函数参数与原函数一致。最终 wrappedMap 的类型通过映射类型 { [K in keyof typeof myMap]: ReturnType<typeof wrapper> } 定义,确保每个键对应正确的包装函数。
实践中的类型关联陷阱与解法
在改进 JS 函数的练习中,答案1虽能推导 WrappedMap,但内部仍需 (fn as any).apply,因为 TypeScript 未将 a、b、c 与其函数参数类型关联。答案2通过先定义 MyMapArgs 映射参数类型,再基于它构建 MyMap,使 TypeScript 理解键与参数的关系,从而移除 as any。这提示:当类型关联复杂时,可引入中间映射来显式建立联系。
Q&A
TypeScript 泛型是什么?怎么理解它?
泛型可以理解为把类型当作变量传给类型定义,就像给函数传参数一样。例如 function identity<Type>(arg: Type): Type { return arg; },其中 Type 就是类型参数,可以自由命名,常用 T。
TypeScript 泛型调用时可以不写类型参数吗?
可以。使用泛型函数时通常省略显式类型声明,TypeScript 会自动推断类型。例如 let output = identity("myString"); 此时 output 被推断为 string 类型。
TypeScript 泛型中 extends 有什么作用?
extends 用于给泛型划定范围,限制其类型。例如 <T extends Lengthwise> 表示 T 必须具有 length 属性,这样在函数内访问 arg.length 才能通过类型检查。任何具有 length 属性的类型(如数组)都满足约束。
TypeScript 泛型除了复用类型还有什么作用?
泛型还能实现类型的联动(绑定)。例如 function identity<Type>(arg: Type): Type 绑定了输入和输出类型;再如 getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) 将 key 绑定到 Type 的键上,传入不存在的键会报错。
如何用 TypeScript 泛型和映射类型为包装函数精确推导类型?
可以结合 keyof、映射类型和 Parameters/ReturnType。例如定义 wrapper<K extends keyof typeof myMap>(key: K, fn: (typeof myMap)[K]),返回函数参数类型为 Parameters<typeof fn>;再用 { [K in keyof typeof myMap]: ReturnType<typeof wrapper> } 定义 wrappedMap 的类型。
在包装函数中为什么需要把 fn 断言为 any?如何避免?
因为 TypeScript 无法自动关联对象键和其函数参数类型,所以即使使用泛型约束,fn.apply 仍可能报错,需要 (fn as any)。避免方法是先创建一个映射参数类型的接口(如 MyMapArgs),再用它生成 MyMap,这样 TypeScript 就能理解键与参数类型的关联,从而去掉 as any。