条件类型
TS中的条件类型就是在类型中添加条件分支,以支持更加灵活的泛型,满足更多的使用场景。内置条件类型是TS内部封装好的一些类型处理,使用起来更加便利。
一、基本用法
当T类型可以赋值给U类型时,则返回X类型,否则返回Y类型。
javascript
T extends U ? X : Y
T
U
X
Y
四个是占位符,分别表示四种类型;T extends U
表示T
类型能被赋值给U
类型,这里还涉及到TS类型兼容性。
这个表达式的意思是,如果 T 可以赋值给 U 类型,则是 X 类型,否则是 Y 类型。来看个实际例子:
T
的具体类型返回不同类型的字符串,也就是字面量类型
javascript
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object";
type A = TypeName<'1'> //"string"
type B = TypeName<1> //"number"
type C = TypeName<true> //"blolean"
type D = TypeName<undefined> //"undefined"
type E = TypeName<()=>void> //"function"
type F = TypeName<{}> //"object "
T
为联合类型时 :
javascript
type G = TypeName<'1'| 1 | true> // "string" | "number" | "boolean"
type H = TypeName<()=>void | {}> // "function" | "object"
根据类型参数判断返回类型:
二、分布式条件类型
什么样的条件类型称为分布式条件类型呢?
答案是:条件类型里待检查的类型必须是裸类型 (naked type parameter
)
1. 什么类型是裸类型?
裸类型是指类型参数没有被包装在其他类型里,比如没有被数组、元组、函数、Promise等等包裹,简而言之裸类型就是未经过任何其他类型修饰或包装的类型。
当分布式条件类型中被检查类型为联合类型,则在运算过程中分解多个分支 。
javascript
type typeName<T> = T extends number ? "X" : "Y" ;
type H = typeName<string | number> // "X" | "Y"
//上面typeName在计算类型时,会分解为如下:
type H = string extends number ? "X" : "Y" | number extends number ? "X" : "Y"
= "X" | "Y"
javascript
// 裸类型参数,没有被任何其他类型包裹,即T
type NakedType<T> = T extends boolean ? "YES" : "NO"
// 类型参数被包裹的在元组内,即[T]
type WrappedType<T> = [T] extends [boolean] ? "YES" : "NO";
2. 分布式如何理解?
分布式条件类型在实例化时会自动分发成联合类型
什么意思呢?
T extends U ? X : Y
使用类型参数A | B | C
实例化 T 解析为 (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)
结合 乘法分配律
理解一下!
接下来结合具体实例我们来看一下分布式条件类型
与 不含有分布式特性的条件类型
javascript
// 裸类型参数,没有被任何其他类型包裹,即T
type NakedType<T> = T extends boolean ? 'YES' : 'NO';
// 类型参数被包裹的在元组内,即[T]
type WrappedType<T> = [T] extends [boolean] ? 'YES' : 'NO';
// 含有分布式特性的,待检查类型必须为"裸类型"
type Distributed = NakedType<number | boolean>; // = NakedType<number> | NakedType<boolean> = "NO" | "YES"(结合一下乘法分配律便于理解与记忆哦~)
// 不含有分布式特性的,待检查的类型为包装或修饰过的类型
type NotDistributed = WrappedType<number | boolean>; // "NO"
搞明白了分布式条件类型,我们编写这样一个类型工具 NonNullable
,即从类型 T
中排除 null 和 undefined
,我们期待的结果如下:
javascript
type a = NonNullable<string | number | undefined | null> // 得到type a = string | number
借助条件类型可以很容易写出来:
javascript
type NonNullable<T> = T extends null | undefined ? never : T
注意: never
类型表示不会是任何值,即什么都没有
三、非分布式条件类型
当T被数组、元组、Promise等包裹时,则运算过程中不会分解成多个分支,则该条件类型为非分布式条件类型。
javascript
type WrappedTuple<T> = [T] extends [boolean] ? "X" : "Y";
type WrappedArray<T> = T[] extends boolean[] ? "X" : "Y";
type WrappedPromise<T> = Promise<T> extends Promise<boolean> ? "X" : "Y";
type G = WrappedTuple<string | boolean>; // "Y"
type K = WrappedArray<string | boolean>; // "Y"
type L = WrappedPromise<string | boolean>; // "Y"
解析:
javascript
//由于计算数据时不会分解成多个分支
// [T] extends [boolean]
//WrappedTuple<string | boolean> 中 string | boolean 不是 boolean 类型,也不是其他原始类型
//T[] extends boolean[]
//WrappedArray<string | boolean> 中 string | boolean 不是 boolean 类型,也不是其他原始类型
//Promise<T> extends Promise<boolean>
//WrappedPromise<string | boolean> 中 string | boolean 不是 boolean 类型,也不是其他原始类型
四、结合泛型使用
1. 过滤出公共类型
在联合类型T
中过滤 出联合类型U
中的成员,过滤出来的成员则组成新的类型。下面例子中,如果类型T
为类型U
的子类型,则返回类型T
,否则返回never
javascript
type Filter<T, U> = T extends U ? T : never;
type B = Filter<"a" | "b" | "c", "a" | "c" | "d"> ; // "a" | "c"
2. 类型删除
在联合类型T
中删除 联合类型U
中的成员,T
类型中的剩余成员则组成新的类型。下面例子中,如果类型T
为类型U
的子类型,则返回never
,否则返回类型T
javascript
type Diff<T, U> = T extends U ? never : T;
type A = Diff<"a" | "b" | "c", "a" | "c" | "d">; // "b"
五、结合 infer 关键字
1. 根据类型参数判断返回类型
- 案例一:
条件类型提供一个infer关键字用来推断类型,我们先来看个例子。我们想定义一个条件类型,如果传入的类型是一个数组,则返回它元素的类型;如果是一个普通类型,则直接返回这个类型。来看下不使用 infer 的话,怎么写:
javascript
type Type<T> = T extends any[] ? T[number] : T;
type test = Type<string[]>; // test的类型为string
type test2 = Type<string>; // test2的类型为string
这个例子中,如果传入 Type 的是一个数组类型,那么返回的类型为T[number],也就是该数组的元素类型,如果不是数组,则直接返回这个类型。这里我们是自己通过索引访问类型T[number]来获取类型的,如果使用 infer 关键字则无需自己手动获取,我们来看下怎么使用 infer:
javascript
type Type<T> = T extends Array<infer U> ? U : T;
type test = Type<string[]>; // test的类型为string
type test2 = Type<string>; // test2的类型为string
这里 infer 能够推断出 U 的类型,并且供后面使用,你可以理解为这里定义了一个变量 U 来接收数组元素的类型。
- 案例二:
javascript
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
const foo = (): string => {
return "hello";
}
const bar = (): number => {
return 42;
}
const str: ReturnType<typeof foo> = "hello"; // string
const num: ReturnType<typeof bar> = 42; // number
在这个例子中,我们定义了一个 ReturnType
类型,它接受一个泛型类型参数 T
。这个类型参数代表一个函数类型,我们通过 T extends (...args: any[]) => infer R
这个条件语句来判断 T
是否是一个函数类型,并提取其返回值类型 R
。如果 T
是一个函数类型,则返回 R
类型,否则返回 never
类型。
然后,我们定义了两个函数 foo
和 bar
,分别返回字符串和数字。我们使用 typeof 操作符来获取这两个函数的类型,并通过 ReturnType
和 ReturnType
来获取它们的返回类型。
最后,我们声明了两个变量 str
和 num
,并分别将它们声明为 ReturnType
和 ReturnType
类型。这样就可以根据不同的函数类型来确定返回类型。
- 案例三:
javascript
type IsOptional<T> = T extends { [k: string]: infer U } ? undefined extends U ? true : false : never;
interface Person {
name: string;
age?: number;
}
const person1: Person = { name: "Alice" };
const person2: Person = { name: "Bob", age: 30 };
const isOptional1: IsOptional<typeof person1> = true; // true
const isOptional2: IsOptional<typeof person2> = true; // false
在这个例子中,我们定义了一个 IsOptional
类型,它接受一个泛型类型参数 T
。这个类型参数代表一个对象类型,我们通过 T extends { [k: string]: infer U }
这个条件语句来判断 T
是否是一个对象类型,并提取其属性类型 U
。然后,我们再使用 undefined extends U ? true : false
来判断属性类型 U
是否包含 undefined
,如果包含则返回 true
,否则返回 false
。最后,如果 T
是一个对象类型,则返回 true
或 false
类型,否则返回 never
类型。
然后,我们定义了一个 Person
接口,它包含一个必须属性 name
和一个可选属性 age
。我们声明了两个变量 person1
和 person2
,分别表示一个只有必须属性的 Person
对象和一个同时包含必须属性和可选属性的 Person
对象。
最后,我们使用 IsOptional
和 IsOptional
分别获取这两个对象的属性是否可选,并将它们声明为 true
或 false
类型。这样就可以根据对象类型的属性是否可选来确定返回类型。
2. 根据条件类型判断函数参数类型
- 案例一:
javascript
type FunctionReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
定义了FunctionReturnType
条件类型,它会检查类型T
是否为函数类型,如果是则通过infer
获取函数的返回值类型R
,否则返回never
类型。具体案例如下:
javascript
type FRT1 = FunctionReturnType<() => number> //number
type FRT2 = FunctionReturnType<(x: string, y: number) => string[]> //string
type FRT3 = FunctionReturnType<() => void>//void
type FRT4 = FunctionReturnType<() => {}>//{}
type FRT5 = FunctionReturnType<1>//never
- 案例二:
javascript
type ArgumentType<T> = T extends (arg: infer U) => any ? U : never;
const foo = (arg: string) => {
return arg.toUpperCase();
}
const str: ArgumentType<typeof foo> = "hello"; // string
在这个例子中,我们定义了一个 ArgumentType
类型,它接受一个泛型类型参数 T
。这个类型参数代表一个函数类型,我们通过 T extends (arg: infer U) => any
这个条件语句来判断 T
是否是一个函数类型,并提取其参数类型 U
。如果 T
是一个函数类型,则返回参数类型 U
,否则返回 never
类型。
然后,我们定义了一个 foo
函数,它接受一个字符串类型的参数,并将其转换为大写字母。我们使用 typeof
操作符来获取 foo
函数的类型,并通过 ArgumentType
来获取其参数类型。
- 案例三:
下面的示例演示在协变位置 上同一类型变量的多个候选类型 将会被推断为联合类型:
javascript
type Foo<T> = T extends { a: infer U, b: infer U } ? U : never;
type t1 = Foo<{ a: string, b: string }>; // string
type t2 = Foo<{ a: string, b: number }>; // string | number
同样在逆变位置 上同一类型变量的多个候选类型 将会被推断为交叉类型:
javascript
type Bar<T> = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never;
type t1 = Bar<{ a: (x: string) => void, b: (x: string) => void }>; // string
type t2 = Bar<{ a: (x: string) => void, b: (x: number) => void }>; // string & number
3. 根据类型的属性来判断函数的返回类型
假设我们有一个名为 fetchData
的函数,它从服务器获取数据并将其解析为特定类型。我们希望函数的返回类型根据请求的不同而有所区别,因此我们可以使用条件类型来实现这一点。
javascript
interface User {
name: string;
age: number;
}
interface Post {
title: string;
content: string;
}
type RequestType = "user" | "post";
const fetchData = <T extends RequestType>(type: T): T extends "user" ? User : Post => {
// 根据请求类型获取不同的数据
if (type === "user") {
return { name: "John", age: 30 } as any;
} else {
return { title: "TypeScript is awesome", content: "..." } as any;
}
}
const user = fetchData("user"); // User 类型
const post = fetchData("post"); // Post 类型
在这个例子中,我们定义了一个名为 fetchData
的函数,它接受一个泛型类型参数 T
,这个类型参数代表请求的类型,可以是 user
或 post
。我们使用条件类型 T extends "user" ? User : Post
来决定返回的类型,如果请求的类型是 user
,则返回 User
类型,否则返回 Post
类型。
然后,我们在函数体中根据请求类型获取不同的数据,返回的类型也会根据请求类型发生变化。
最后,我们分别调用 fetchData
函数,并将其返回值分别赋值给 user
和 post
变量,这样就可以根据请求类型获取不同的数据,并且保证返回类型的正确性。
4. 根据类型的成员来判断其他类型
假设我们有一个名为 HasName
的类型,它表示一个具有 name
属性的类型。我们希望定义一个名为 PickWithName
的类型,它接受一个泛型类型参数 T
,并从中选择具有 name
属性的子类型。
javascript
interface User {
name: string;
age: number;
}
interface Post {
title: string;
content: string;
}
type HasName = { name: string };
type PickWithName<T> = T extends HasName ? T : never;
type UserWithName = PickWithName<User>; // User 类型
type PostWithName = PickWithName<Post>; // never 类型
在这个例子中,我们定义了一个 HasName
类型,它代表具有 name
属性的类型。然后,我们定义了一个名为 PickWithName
的类型,它接受一个泛型类型参数 T
,并使用条件类型 T extends HasName ? T : never
来判断 T
是否具有 name
属性,如果是,则返回 T
,否则返回 never
类型。
最后,我们分别使用 PickWithName
类型别名 PickWithName
来选择具有 name
属性的子类型,并将其分别赋值给 UserWithName
和 PostWithName
变量。
5. 根据类型的可选属性来添加或删除属性修饰符
假设我们有一个名为 Person
的类型,它有两个属性 name
和 age
,其中 name
属性是必需的,而 age
属性是可选的。我们希望定义一个名为 MakeFieldsOptional
的类型,它接受一个泛型类型参数 T
,并将 T
中所有可选属性的修饰符从必需改为可选,将所有必需属性的修饰符保持不变。
javascript
type Person = {
name: string;
age?: number;
};
type MakeFieldsOptional<T> = {
[P in keyof T]?: T[P];
};
type OptionalPerson = MakeFieldsOptional<Person>; // { name?: string; age?: number; }
在这个例子中,我们首先定义了一个 Person
类型,它有一个必需的 name
属性和一个可选的 age 属性。
然后,我们定义了一个名为 MakeFieldsOptional
的类型,它使用映射类型 [P in keyof T]?: T[P]
,将 T
中所有可选属性的修饰符从必需改为可选,将所有必需属性的修饰符保持不变。
最后,我们使用 MakeFieldsOptional
类型别名来将 Person
类型中所有可选属性的修饰符从必需改为可选,将其赋值给 OptionalPerson
变量。
6. 元组转为联合类型 tuple转union
[string, number] -> string | number
javascript
type ElementOf<T> = T extends Array<infer P> ? P : never;
type Tuple = [string, number];
type TupleToUnion = ElementOf<Tuple>; // type TupleToUnion = string | number
7. 联合类型转成交叉类型
string | number -> string & number
javascript
type T1 = { name: string };
type T2 = { age: number };
type ToIntersection<T> = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never;
// 由于U需要同时满足T1的定义、T2的定义,因此U需要包含T1、T2所有的类型,因此T3就是T1 & T2
type T3 = ToIntersection<{ a: (x: T1) => void, b: (x: T2) => void }>; // type T3 = T1 & T2
重点:
U extends any
是具有分布式有条件类型特性,因为待检查类型U
为裸类型(U extends any ? (k: U) => void : never) extends ((k: infer I)=> void)
最后一个extends
前面作为待检查类型,因为被函数包装,因此不具有分布式有条件类型特性
javascript
type UnionToIntersection<U> = ((k: string) => void | (k: number) => void) extends ((k: infer I) => void) ? I : never;
根据 逆变特性 推断出的 I
应该具备 string
和 number
的类型,故为交叉类型 string & number
,而该交叉类型在 vscode 中表现为 never
六、结合keyof关键字
1. 获取对象属性类型
keyof主要是获取某个对象/类型的属性名来构成新类型。我们可以使用条件类型和 keyof 关键字来获取对象的属性。具体案例如下:
javascript
type PropertyType<T, K extends keyof T> = K extends keyof T ? T[K] : never;
上面代码定义了类型为PropertyType<T, K extends keyof T>
,通过检查K是否是T的一个属性名,如果是则返回该属性类型,否则返回never
。
javascript
type Obj = { a: string; b: number };
type A = PropertyType<Obj, "a">; // string
type B = PropertyType<Obj, "c">; // never
2. 实现映射类型
映射类型是泛型类型的一种,可用于把原有的对象类型映射成新的对象类型。我们可以使用条件类型和 keyof
关键字来实现Partial
类型,Partial
类型是TS工具类之一。具体案例如下:
javascript
type Partial<T> = {
[K in keyof T]? : T[K]
}
定义类型Partial
,遍历T
中所有属性,然后通过?
将所有属性变成可选属性。
javascript
type obj = {a:string,b:number}
type R = Partial<obj>//{a?:string,b?:number}
注意:Partial
是TS的工具类,所以声明Partial
是会报错的,可以换个标识符名称。
七、内置条件类型
javascript
// 找出T中不包含U的部分
type Diff<T, U> = T extends U ? never : T;
type R1 = Diff<'a' | 'b' | 'c' | 'd', 'a' | 'b' | 'c'>; // R1: 'd'
// 由于U中的'a','b','c'都是T中的子类型,因此R1的类型就是'd'
// 找出T中包含U的部分
type Filter<T, U> = T extends U ? T : never;
type R2 = Filter<'a' | 'b' | 'c' | 'd', 'a' | 'b' | 'c'>; // R2: 'a' | 'b' | 'c'
1. Exclude
- 从T中排除掉U,和上面的Diff相同
javascript
type Exclude<T, U> = T extends U ? never : T;
type R3 = Exclude<'a' | 'b' | 'c' | 'd', 'a' | 'b' | 'c'>; // R3: 'd'
2. Extract
- 从T中找出包含U的部分,和上面的filter相同
javascript
type Extract<T, U> = T extends U ? T : never;
type R4 = Extract<'a' | 'b' | 'c' | 'd', 'a' | 'b' | 'c'>; // R4: 'a' | 'b' | 'c'
3. NonNullable
- 从T中找出不为null和undefined的参数
javascript
type NonNullable<T> = T extends null | undefined ? never : T;
type R5 = NonNullable<'a' | null | undefined | 'd'>; // R5: 'a' | 'd'
4. ReturnType 和 Parameters
- infer:推断的意思,是一个关键字
- ReturnType 获取函数的返回类型
- Parameters 获取函数参数类型,返回一个元组
javascript
// 1.1
// ReturnType 获取函数的返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : T;
function getUser() {
return {
name: '张三',
age: 10
}
}
// TS可以从参数中推断返回值类型
type ReturnUser = ReturnType<typeof getUser>; // type ReturnUser = {name: string;age: number;}
// 1.2
// Parameters 获取函数参数类型,返回一个元组
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
function getPerson(a: string, b: number) {
return {
name: '李四',
age: 18
}
}
type ParamsType = Parameters<typeof getPerson>; // type ParamsType = [a: string, b: number]
5. InstanceType 和 ConstructorParameters
- InstanceType 获取构造函数的实例类型
- ConstructorParameters 获取类的构造函数的参数类型
javascript
namespace e {
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
getName() {
console.log(this.name);
}
}
// ConstructorParameters 获取类的构造函数的参数类型
type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (
...args: infer P) => any ? P : never;
type Params = ConstructorParameters<typeof Person> // type Params = [name: string]
// InstanceType 获取构造函数的实例类型
type Instance = InstanceType<typeof Person>;
let instance: Instance = {
name: '张三',
getName() {}
}
}