0%

条件类型

条件类型

条件类型是什么

Typescript 2.8 引入了条件类型

条件类型是一个类型层面的表达式,其语法和作用都类似于三目运算。

type T = <condition> ? <satisfy> : <non-satisfy>

在条件满足的时候计算的类型为表达式的类型,否则为 的类型。到目前为止,condiation 部分只支持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;