泛型提供了一种将类型参数化的能力,在其他语言中最基本的用途是定义容器类型,使得工具函数可以不必知道被操作的变量的具体类型。JavaScript 中的数组或 Promise 在 TypeScript 中都会被表述为这样的泛型类型,例如 Promise.all 的类型定义可以写成:
function all<T>(values: Array<T | Promise<T>>): Promise<Array<T>>
可以看到类型参数可以被用来构造更复杂的类型,进行集合运算或嵌套。
默认情况下,因为类型参数可以是任意的类型,所以不能假定它有某些属性或方法,也就不能访问它的任何属性,只有添加了约束才能遵循这个约束去使用它,同时 TypeScript 会依照这个约束限制传入的类型:
interface Lengthwise {
length: number
}
function logLength<T extends Lengthwise>(arg: T) {
console.log(arg.length)
}
约束中也可以用到其他的类型参数或使用多个类型参数,在下面的代码中我们限制类型参数 K 必须是 obj 的一个属性名:
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
除了在函数上使用泛型之外,我们还可以定义泛型类型:
type Partial<T> = {
[P in keyof T]?: T[P];
}
当定义泛型类型时我们实际上是在定义一种处理类型的「函数」,使用泛型参数去生成新的类型,这也被称作「元编程」。例如 Partial 会遍历传入类型 T 的每一个属性,返回一个所有属性都可空的新类型:
interface Person {
name: string
}
const a: Person = {} // 报错 Property 'name' is missing in type '{}' but required in type 'Person'.
const b: Partial<Person> = {}
前面我们提到的 Pick 和 Extract 都是这样的泛型类型。
在此之外 TypeScript 甚至可以在定义泛型类型时进行条件判断和递归,这使得 TypeScript 的类型系统变成了图灵完备的,可以在编译阶段进行任何计算。
你可能会怀疑这样复杂的类型真的有用么?其实这些特性更多地是提供给库开发者使用的,对于 JavaScript 社区中的 ORM、数据结构,或者是 lodash 这样的库来说,如此强大的类型系统是非常必要的,lodash 的 类型定义 行数甚至是它本身代码的几十倍。
如果想要简单调试ts的特性,可以在这里TypeScript练习
在线调试。
网友评论