条件类型,分布式条件类型,infer
1.条件类型
条件类型的语法类似于我们平时常用的三元表达式:
ts
ValueA === ValueB ? Result1 : Result2;
TypeA extends TypeB ? Result1 : Result2;
条件类型绝大部分场景下会和泛型一起使用,泛型参数的实际类型会在实际调用时才被填充(类型别名中显式传入,或者函数中隐式提取),而条件类型在这一基础上,可以基于填充后的泛型参数做进一步的类型操作:
ts
type LiteralType<T> = T extends string ? 'string' : 'other'
type Res1 = LiteralType<'给我一个div'> // "string"
type Res2 = LiteralType<999> // "other"
同三元表达式可以嵌套一样,条件类型中也常见多层嵌套:
ts
export type LiteralType<T> = T extends string
? 'string'
: T extends number
? 'number'
: T extends boolean
? 'boolean'
: T extends null
? 'null'
: T extends undefined
? 'undefined'
: never
type Res1 = LiteralType<'给我一个div'> // "string"
type Res2 = LiteralType<999> // "number"
type Res3 = LiteralType<true> // "boolean"
2.分布式条件类型
提示
分布式条件类型,也称条件类型的分布式特性,是条件类型在满足一定情况下会执行的逻辑
1. 分布式作用的条件
例子:
ts
type Condition<T> = T extends 1 | 2 | 3 ? T : never
/**
* Res1: 1 | 2 | 3
* 理论上这里应该返回 1 | 2 | 3 | 4 | 5 但是由于代码执行时遵循了分布式条件特性,
* 返回结果为 1 | 2 | 3
*/
type Res1 = Condition<1 | 2 | 3 | 4 | 5>
/**
* 这里没有遵循条件时分布特性 返回 never
*/
type Res2 = 1 | 2 | 3 | 4 | 5 extends 1 | 2 | 3 ? 1 | 2 | 3 | 4 | 5 : never
条件分布式起作用的条件
- 类型参数需要通过泛型参数的方式传入,而不能直接进行条件类型判断(如 Res2 中)
- 类型参数需要是一个联合类型
2.条件分布式的效果
条件分布式特性会将联合类型拆开来,每个分支分别进行一次条件类型判断,再将最后的结果合并起来:
ts
type Demo<T> = T extends string ? '123' : '456'
type res = Demo<string | number> // "123" | "456"
/**
* 以上代码在执行时相当于将传入的 string | number类型拆开类执行了两次 Demo的逻辑
* 再合并为联合类型返回:
*/
// 执行了一下逻辑:
(string extends string ? '123' : '456') | (number extends string ? '123' : '456')
内置的工具类型Exclude
,Extract
等就是利用了分布式条件特性实现
ts
type a = 1 | 2 | 3
type b = 2 | 3 | 5
/**
* type Extract<T, U> = T extends U ? T : never;
*/
type x = Extract<a, b> // 2 | 3
3.阻止分布式条件作用
在达到特定的条件下,分布式特性会起作用,但有时可能并不需要这样的特性,只是单纯的封装一个类型来判断两个联合类型的兼容性,此时可以在条件类型中的extends
关键字两侧给类型添加[]
来阻止分布式特性:
ts
type Demo<T, U> = T extends U ? 'true' : 'false'
// 遵循分布式特性返回 "false" | "true"
type c = Demo<1 | 2, 3 | 2 | 4>
/* extends关键字两侧的类型添加[]让其成为元组来阻止分布式特性 */
type Demo1<T, U> = [T] extends [U] ? 'true' : 'false'
// "false"
type d = Demo1<1 | 2, 3 | 2 | 4>
3.infer关键字
TypeScript
中支持通过infer
关键字来在条件类型中提取类型的某一部分信息,简化了多条件类型的情况
需求: 定义类型别名Res<T>
,当泛型T
为数组时提取数组项的类型,否则直接返回传入的类型
ts
type n = number[]
type s = string[]
type Res<T> = T extends number[] ? number : T extends string[] ? string : T
type c = Res<n> // number
type d = Res<s> // string
type f = Res<boolean> // boolean
以上代码使用infer
关键字简化:
ts
type n = number[]
type s = string[]
/* 使用infer推断出数组项的类型 */
type Res<T> = T extends Array<infer R> ? R : T
type c = Res<n> // number
type d = Res<s> // string
type f = Res<boolean> // boolean
提示
infer
是 inference
的缩写,意为推断,如 infer R
中 R
就表示 待推断的类型。 infer
只能在条件类型中使用
1. 推断函数参数类型
- 所有参数组成的元组类型
ts
type Func = (...args: any[]) => any
/**
* infer R推断的时剩余参数args的类型,是一个元组
*/
type FuncParams<T extends Func> = T extends (...args: infer R) => any ? R : never
// [val: string, v: number]
type c = FuncParams<(val: string, v: number) => void>
- 指定位置参数的类型
ts
type Func = (...args: any[]) => any
/**
* 推断第二个参数的类型,被推断参数后面使用 ...any[] 表示剩余的类型集合
*/
type FuncParams<T extends Func> = T extends (...args: [any, infer R, ...any[]])
=>
any ? R : never
// number
type c = FuncParams<(val: string, v: number, c: boolean) => void>
2. 推断函数返回值类型
ts
type FuncParams<T extends Func> = T extends (...args: any[]) => infer R ? R : never
// string | number
type c = FuncParams<(val: string) => string | number>
3. 推断数组类型
- 数组项类型
ts
type Item<T> = T extends Array<infer R> ? R : never
// string | number | boolean
type It = Item<[string, number, boolean]>
- 数组项类型调换位置
ts
type SwapStartAndEnd<T extends any[]> = T extends [
infer Start,
...infer Center,
infer End
]
? [End, ...Center, Start]
: T
// ["end", number, boolean, string]
type Res = SwapStartAndEnd<[string, number, boolean, 'end']>