类型体操入门 —— extends的三种用法
- Authors
- Name
- White Play
前置知识
extends关键字在ts类型系统中主要有三个作用
其一,他可以用来给泛型做限制。
比如有一个比较函数,希望比较两个参数的length值哪个大。
function compare<T extends { length: number }>(a: T, b: T) {
return a.length > b.length ? a : b
}
通过让T继承 {length:number}
,我们就限制了T的类型必须包含length属性,这样函数内部就可以大胆的访问length属性了。
其二,和很多面向对象编程语言的extends相同,我们用它来继承父类。这个不多说了,大家都会。
class Animal {
eat(food: string) {
console.log(`eat ${food}~`)
}
}
class Dog extends Animal {}
const dahuang = new Dog()
dahuang.eat('meet')
其三,就是在类型系统中用来做逻辑判断,类似三元运算符。
a === b ? a : b
type a extends type b ? a : b
不同之处在于我们并不是拿a和b做比较它们是否相等 ,而是在判断a是否是b的子类型。
// 还是用上面狗狗做例子
type R = typeof dahuang extends Animal ? true : false //true
值得注意的事,如果是联合类型参与了extends的三元运算,那么类型系统会把每一项都计算一遍,最终把结果联合到一起。
type Cond<T> = T extends U ? X : Y
type MyType = Cond<A | B>
// 等同于 Cond<A> | Cond<B>
// 等同于 (A extends U ? X : Y) | (B extends U ? X : Y)
类型体操
第一题
实现一个Exclude
// 示例
type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 期望'b' | 'c'
我们只要把第一个泛型接收的联合类型和第二个泛型做比较即可,相等则排除掉(即返回never),不等则返回。
type MyExclude<T, U> = T extends U ? never : T
第二题
实现一个Awaited,即总能把Promise内部的类型返回出去。
这道题值得注意的是,测试用例不仅仅是操作Promise类型,还有Promise嵌套Promise,甚至多层嵌套。以及不是标准实现的Promise,即Thenable
对象。
// 示例
type ExampleType = Promise<string>
type Result = MyAwaited<ExampleType> // string
所以一方面我们要结合infer
来推导Promise内部类型,另一方面应该使用类型递归
来处理可能遇到的Promise嵌套问题。最后我们应该使用PromiseLike而非Promise以兼容Thenable
,即不标准的Promise。
// 答案如下,稍微上了点强度
type MyAwaited<T> =
T extends PromiseLike<infer K> ? (K extends PromiseLike<any> ? MyAwaited<K> : K) : never
第三题
实现If,示例如下。
type A = If<true, 'a', 'b'> // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'
这道题在考我们三元表达式。值得注意的是我们需要给C做一点限制,避免传入非布尔的类型。
type If<C extends Boolean, T, F> = C extends true ? T : F