一、 理解
- 类型系统的函数
- 比如某一类对象,在不清楚这类对象具体有哪些key,以及key对应的类型时,方便用参数来代替,并且在真正使用的时候,传入具体的类型或key。
- 当准备使用 any 时,可以考虑用泛型代替
- 对于某个函数的类型是泛型时,当调用这个函数时不用传泛型参数,会自动根据这个函数的参数类型去推断!
- 函数泛型写法
function identity<T>(arg: T): T { return arg; } interface GenericIdentityFn { <T>(arg: T): T; } let myIdentity: <U>(arg: U) => U = identity; let output = identity<string>("myString"); // 或 let output2 = identity("myString");- 刚开始以为,既然函数可以这样反推类型,为什么对象不可以?
- 仔细看看后,在上面的例子中,发现函数和对象的使用时机和定义的方式是不一样的。函数在定义值时就已经知道了类型,或者在定义值的时候也可以同时定义类型。但是对象没有。
type obj<p> = { t:p, fn: (a:p) => p } const a:obj<string> = { t: "2", fn: function (c) { // 在给函数赋值时,就已经知道这个函数类型 console.log(c) return c } } a.fn("aa") // 在定义值得时候同时定义类型 const fc = function<p> (k: p):p { return k } fc(3) // 当函数像使用对象那样定义类型,再赋值时, type fn<p>= (k:p)=>void; // 泛型也必须传参数 const fn:fn<string> = function (k:string) { return k } fn("2")
二、泛型与js某些相同的语法
- 继承
- interface 可以使用继承 extends, type不可以使用继承
- 使用继承可以来表达默认值和进行一些类型的约束
- 默认值, 再写树形节点的类型时,对childField字段可能是 "child"、"children"...等不同的字段时,可以先用个默认字段 children 代替。在真正使用时,明确知道是那个知道时再作为参数传入
// 树形节点的类型
type anyObj<T = Record<string, unknown>> = T & {
[k:string]:any
}
type treeNode<a, p extends string = "children"> = {
[k in p]:treeNode<a, p>[];
} & anyObj<{
active: a;
expand?: boolean;
}>
/* [k in p]:treeNode<a, p>[]; // 遍历 传入的参数p 的类型值,
* 虽然它的值肯定只用一个。但是直接写 [k:p]:treeNode<a, p>[]; 会报 k的值只能为 string or number
*/
// 不把 {active: a; expand?: boolean;} 和 [k in p]:treeNode<a, p>[]; 写一起是因为 写在一起会报错的,具体原因,还没理解清楚
三、功能强大,常用的关键子
- keyof
提出对象类型的 key 值作为联合类型
type obj = {
a: number;
b: string;
}
type a = keyof obj ; // "a" || "b
- in
遍历的作用
type arr = "a" | "b"
type obj = {
[k in arr]: string;
}
// 可以理解 keyof 和 in 功能相反互补
- extends:继承
- infer:条件类型
比较复杂一些,可看这篇文章 [TypeScript] 在条件类型中使用 infer 关键字 比较详细清楚的讲解类型的文章