持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第 3 天
泛型就像函数
const f = (a, b) => a + b
const result = (1, 2) // 3
如果你能看懂 JS 的函数,那么你就能看懂 TS 的泛型。
type F<A, B> = A | B
type Result = F<string, number> // string | number
它们的唯一区别就是 () 变成了 <>, const 变成了 type 。
我们可以认为函数是接受其它代码的代码。
我们可以认为泛型是接受其它类型的类型。
所以 JS 的函数和 TS 的函数,只是操作的对象不一样,JS 操作值,TS 操作类型。
函数的本质是什么?
推后执行的、部分待定的代码。
// 当我们写如下代码的时候,这个代码在我们写的时候
cosole.log(1) // 只要执行到这一行,是不是就运行了
// 如果我们咬他延后执行,该怎么办?
const f = () => console.log(1) // 如果把它声明成一个函数,它的定义和它的执行就分离了,我只定义,并没有执行。
// 推迟执行
setTimeout( () => {
f()
}, 3000 ) // 3s 后执行这个函数
所以函数的本质在面向过程式编程里面,它就是把这个执行时机往后推。
它还可以把代码的一块扣出来等到你未来去填这一块
const f = () => console.log(1) // 你永远打印 1
const f2 = x => console.log(x) // 把里面的 值 扣出来 用 变量 x,这里的 x 就是部分待定的代码
// 等你想执行的时候你可以把你扣的那一块,在用你想传的值 给填上去
f2(2222)
const f3 = (fn) => fn(1)
// 这里使用 console.log 只是为了举例,实际使用可能有问题
f3(console.log) // 这样的话等 执行 f3 的时候,是不是相当于执行 console.log(1)
const f4 = (fn, n) = fn(n)
f4(console.log, 222222)
// 其实函数的本质是我现在有些东西决定不了,或者我希望是别人决定的。
那么泛型的本质就是推后执行的、部分待定的类型。
为什么会有泛型
function echo(whatever: number | string | boolean) {
return whatever
}
// 这个 echo 函数的类型是什么?
我们所认为的
参数类型 number | string | boolean
返回值类型 number | string | boolean
以上这种形式不对,因为这种形式隐含了一些错乱的情况,比如说我是不是可以接受一个 boolean 返回一个 number,我是不是可以接受一个 number 返回一个 boolean。
但是我们想要的情况是一一对应的,接受什么就返回什么。
参数类型 number string boolean
返回值类型 number string boolean
我们需要对你的类型进行精细化的管理,而非笼统的。
那我做类型收窄不行吗?
// 就算这样写,依然会得到错误的答案
function echo(whatever: number | string | boolean) {
switch (typeof whatever) {
case 'number':
return whatever
break;
case 'string':
return whatever
break;
case 'boolean':
return whatever
break;
}
}
依然存在错乱的情况
通过以上例子得出:
没有泛型,有些奇怪的需求就无法满足,没有泛型的类型系统,就如同没有函数的编程语言。
简单的泛型
代入法
type Union<A, B> = A | B
type Union3<A, B, C> = A | B | C
type Intersect<A, B> = A & B
type Intersect3<A, B, C> = A & B & C
interface List<A> {
[index: number]: A
}
interface Hash<V> {
[key: string]: V
}
interface List<A> {
[index: number]: A
}
type X = List<string>
type Y = List<string | number>
这个的意思就是
// 使用代入法
interface X {
[index: number]: string
}
interface Y {
[index: number]: string | number
}
默认类型
如果不传就会报错
那能不能给参数一个默认值呢?
// JS 函数参数给默认值
function a(b=1) {return b}
// 触类旁通
// <A = string> 类型参数的默认值
interface List<A = string> {
[index: number]: A
}
// 以下都可以,没有报错
type X = List
// 可传可不传
type X1 = List<string>
type X2 = List<string | number>
interface Person {
name: string
}
type X3 = List<Person>
在泛型中使用 extends
extends 不要读作继承,因为继承只对面向对象有意思,可以读作 包含于。
type Person = {name: string}
// = 右边的表达式 叫做 条件类型 Conditional Types
type LikeString<T> = T extends string ? true : false
type LikeNumber<T> = T extends number ? 1 : 2
type LikePerson<T> = T extends Person ? 'yes' : 'no'
type R1 = LikeString<'hi'> // true
type R2 = LikeNumber<true> // false
type S1 = LikeNumber<6666> // 1
type S2 = LikeNumber<false> // 2
type T1 = LikePerson<{ name: 'hone', xxx: 1 }> // yes
type T2 = LikePerson<{ xxx: 1 }> // no
// unknown 是包含所有类型的, 所以这个 T 必然 包含于 unknown
type ToArray<T> = T extends unknown ? T[] : never
type Result = ToArray<string | number>
// 代入过程
// type Result = (string | number) extends unknown ?...
// type Result = (string extends unknown ?...)
// | (number extends unknown ?...)
// type Result = string[] | number[]
为何是上面的这种情况?
我们在使用泛型的时候,很有可能是在函数里面使用的
type ToArray<T> = T extends unknown ? T[] : never
// 伪代码
function f(a: T): ToArray<T>{
if(typeof a === 'string') {
return string[]
} else if(typeof a === 'number') {
return number[]
}
}
所以根据我们写代码的习惯来看,这个 TS 的预判是对的。它确实应该分开操作,它其实在模拟 JS 函数的思路先把泛型分别收窄分别做计算。
type X1 = LikeString<never> 如果传的是 never, 把它理解成并集,就会发现它是空的,没有东西并上没有东西,那如果把它分散开,并集是不用分散的,所以就直接返回一个 never。
type Result = ToArray<string | number> 之所以要分开,是因为它有两个选项,可以分两个分支,type X1 = LikeString<never> 它只有一个 never,没有分支,结果就必然是一个 never。
type ToArray<T> = T extends unknown ? T[] : never
type Result = ToArray<never>
// type Result = never extends unknown ? ...
// type Result = 空集没有元素直接返回 never
// type Result = never
规则:
- 若 T 为 never,则表达式的值为 never
- 若 T 为 联合类型,则分开计算
如果你用的是泛型而这个泛型又正好跟联合类型用到一起了,它就很像乘法:
乘法的分配律 : (A + B) * C = A * C + B * C
泛型之于联合类型的分配律 : (string | number) extends ? 这里就不能直接 extends, 因为 string 和 number 没有交集,必须先分配。
乘法中的零: 0 * C = 0
遇到 never 永远是 never
注意:只对泛型有效
为何会出现以上情况?因为没有用函数
// 发现如果不是泛型 无法写
// 只能用自己骗自己的写法
// 那么这里的值就没有办法像泛型那样灵活
type X2 = string | number extends unknown ? (string | number)[] : never
如果你不是泛型,你就根据最基础的知识,看它的集合是否包含于另外一个集合之中。
在泛型中使用 keyof
// 获取 Person 所有的 key
type Person = { name: string, age: number }
type GetKeys<T> = keyof T
type Result = GetKeys<Person>
// ^-- 'name' | 'age'
在泛型中使用 extends keyof
type Person = {name: string, age: number}
// K extends keyof T 泛型约束 我要约束这个 K
// 这里的 T[K] 就是 Person['name'] Person['age']
type GetKeyType<T, K extends keyof T> = T[K]
type Result1 = GetKeyType<Person, 'name'>
// ^-- string
type Result2 = GetKeyType<Person, 'age'>
// ^-- number```