条件类型
条件类型是什么
Typescript 2.8 引入了条件类型
条件类型是一个类型层面的表达式,其语法和作用都类似于三目运算。
type T = <condition> ? <satisfy> : <non-satisfy>
在条件满足的时候计算的类型为extends
一种运算。
条件类型常与泛型一起使用:
type IsNumber<a> = a extends number ? true : false
type A = IsNumber<1> // 计算为true
type B = IsNumber<{}>// 计算为false
extends 字面意思是扩展、继承的意思,a extends b
可读做为 a 继承于 b,在条件类型的语境下,可读作a继承于b吗?
,因此上面的例子可读作:a 继承于number 吗?如果是,那么IsNumber计算为true,否则计算为false。
那么如何判断a 是否继承于 b 呢?就是看a类型的值是否可以赋值给b类型,或者接受b类型作为参数的函数是否可以接受a类型。如果a 继承于 b 那么 b 是父类型,a 是子类型。a相对于b更具体,b相对于a更抽象。a 的值域是b的值域的子集(非真子集)。
type A = 1|2 extends number ? true : false //true
type B = 1|2 extends 1|2|3 ? true : false // true
记 1|2 这个联合类型为a,number 为 b, a extends b 是满足的,因为接受number 为参数的地方,也可依接受1或2, 反之则未必。
条件类型的使用例子
如何用条件类型实现一个判断类型是否一样的泛型类型呢?
type Equal<A,B> = ?
type T1 = Equal<number,number> //期望: true
type T2 = Equal<string,number> //期望: false
type T3 = Equal<1|2,2|1> // 期望: true
interface Animal {
live(): void;
}
interface Dog extends Animal {
woof(): void;
}
type T4 = Equal<Animal,Dog> // 期望: false
对于两个类型A和B,如何定于其是否一样呢?我认为可以这样:如果A类型的值可以赋值给B类型的变量,同时B类型的值可以赋值给A类型的变量,那么A和B类型是一样的。或者说,A类型的取值范围和B类型的取值范围一模一样,那么A和B类型是一样的。
type A = 1 | 2
type B = 2 | 1
declare let a:A
declare let b:B
a = b;//OK
b = a;//OK
这样,很容易想到一个实现:
type Equal<A,B> = A extends B
? (B extends A ? true : false)
: false
既当A extends B 并且 B 也 extends A 的时候 A 和 B 是一样的。
type Equal<A,B> = A extends B
? (B extends A ? true : false)
: false
type T1 = Equal<number,number> //期望: true
type T2 = Equal<string,number> //期望: false
type T3 = Equal<1|2,2|1> //期望: true
// ^ boolean
interface Animal {
live(): void;
}
interface Dog extends Animal {
woof(): void;
}
type T4 = Equal<Animal,Dog> //期望: false
T3 计算出来的类型为boolean 而不是 true, 这是怎么回事呢?这是因为条件类型遇到联合类型的时候是先进行分配后计算的(见Distributive Contional types)。
// T3 = Equal<1|2,2|>
// => Equal<1,2|1> | Equal<2,2|1>
// => Equal<1,2>|Equal<1,1> | Equal<2,2>|Equal<2,1>
// => false |true | true |false
// => boolean
可以通过加方括号来防止分配:
type Equal<A,B> = [A] extends [B]
? ([B] extends [A] ? true : false)
: false
为什么方括号可以防止分配呢,因为[A] 形成了新类型:一个单元素的元组,没有了联合类型自然就没有了分配。
按照前面对类型一样的定义,Equal<any, 1> 是返回true的,可如果我们想给any 开个后门,只让Equal<any,any> 返回true,其它情况返回false,怎么做呢?加入有个IsAny 就好了
type IsAny<A> = ?
type BothAny<A,B> = [IsAny<A>,IsAny<B>] extends [true,true] ? true : false
type Equal<A,B> = BothAny<A,B> extends true ? true :
[A] extends [B]
? ([B] extends [A] ? true : false)
: false
IsAny 当然不能用 A extends any 来做,因为无论A是什么类型,A extends any 总是成立。但是我们可以这样:
type IsAny<A> = (<T>()=>T extends A ? "whatever" :"whatsoever") extends
(()=> "whatever") ? true : false
因为只有A为any的时候 (<T>()⇒T extends A ? "whatever" : "whatsover")
被计算为 (<T>()=>T extends any "whatever")
进一步被计算为(<T>()=>"whatever")
最后整个表达式被计算为true。
这里还有另外一中做法:
export type Equals<X, Y> =
(<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? true : false;