never 代表不可达,比如函数的异常抛出就是 never
void 代表空 可以时 undefined 或 never
any 代表任意类型,任何类型都可以赋值给它,但是不能赋值给其他的类型
unknown 未知类型,任何类型都可以赋值给它,但是不能赋值给别的类型
any 和 unknown 的区别: any 和 unknown 都代表任意类型,但是 unknown 只能接收任意类型的值,而 any 除了可以接收任意类型的值,也可以赋值给任意类型(除了 never)。类型体操中经常用 unknown 接受和匹配任何类型,而很少把任何类型赋值给某个类型变量。
1、提取类型
type GerValueResult<P> = P extends Promise<infer value> ? value : never
type Result = GetValueType<Promise<number>>
需要注意这里的 infer
他会自动推断 那也是你需要去传入合适的类型才会推断
type GerValueResult<P> = P extends Promise<infer value> ? value : never
type Result = GetValueType<number>
// Result = never; 这显然和我们的预期不符合
还有这种
type GetValue<P> = p extends Array<infer value> ? value : never
type Value = GerValue<number>
// Value = never
// 要是想要获得合适的值就必须传入 合适的参数
type Value = GerValue<number[]>
type Value = GerValue<Array<number>>
// 这两者都可以
infer 会根据合适的类型去推断 当传入的类型不完整的时候就会出现不符合预期值。
TypeScript 类型的模式匹配就是通过使用 extends ?
这样的语法来去对类型参数匹配,将其结果保存在 infer 的局部变量中
2、提取数组第一个元素
type GetArrayFirst<T extends unknown[]> = T extends [infer Value, ...unknown[]] ? Value : never
type arr = [1, 2, 3]
type FirstArray = GetArrayFirst<arr>
// FirseArray = 1
T extends unknown[]
用来约束类型为数组 后面的 extends 用来结合 ?
用来判断
any 和 unknown 的区别: any 和 unknown 都代表任意类型,但是 unknown 只能接收任意类型的值,而 any 除了可以接收任意类型的值,也可以赋值给任意类型
类似的可以取出数组的最后一个
type Arr = [1, 2, 3]
type GetLastArray<T extends unknown[]> = T extends [...unknown[], infer Last] ? Last : never
type Last = GetLastArray<Arr>
或者取出排除最后一个的剩余数组
type Arr = [1, 2, 3]
type GetArray<T extends unknown[]> = T extends [] ? [] : T extends [...infer Rest, unknown] ? Rest : unknown
type Rest = GetArray<Arr>
1、判断某个字符串是否以某个前缀开头
type HasValueStr<T extends string, Y extends string> = T extends `${Y}${string}` ? true : false
type IsValue = HasValueStr<'Hellow word', 'Hellow'>
这里使用了 ${Y}${string}
前一个 ${Y}
表示继承的是 Y 的类型 后一个 ${string}
表示任何字符串
2、字符串的匹配的问题
type Replace<Str extends string, From extends string, To extends string> = Str extends `${infer Prefix}${From}${infer foot}`
? `${Prefix}${To}${foot}`
: Str
// 这里的意思就是通过传入的三个参数 用 From 去将字符划分三个区域
// 目标 划分标识 替换字符
type ReplaceResult = Replace<'Object is ?', '?', 'Value'>
// Object is Value
3、替换字符串的空白字符 Trim
空白字符可能很多 只能一个一个匹配递归去除
type TrimString<Str extends string> = Str extends `${infer Result} ${' ' | '\n' | '\t'} ` ? TrimString : Str
总结下来 使用 extends
后面的这个很重要 它必须能够代表你传入的类型 你可以使用 infer 来去使用这个类型中的一小撮类型 比如 ${infer Value}${string}
然后你传入了一个这样的值 Hellow wwwww sdsd
那么你使用 infer 声明的 Value
变量的值就是 Hellow 后面的 ${string}
相当于占位符。用于解释器后都是字符串类型
1、提取参数的类型
type GetParamterFunc<Func extends Function> = Func extends (...args: infer Value) => void ? Value : never
type FuncParamter = GetParamterFunc<(name: string) => void>
这里的(name: string)=>void
也是一种类型--函数类型
2、提取返回值
type GetReturnFunc<Func extends Function> = Func extends (...arg: any[]) => infer Return ? Return : never
type FuncReturnValue = GetReturnFunc<(name: number) => void>
这里又一个注意点 这里参数使用了 any
而不是 unknown
因为这里是做参数的类型 而参数又被赋予其他的类型上 unknown 只能用来被标识 不能用来接收
使用了 unknown
那么就会返回 never 类型。
3、 关于 this 的问题
class Dong {
name: string
constructor() {
this.name = 'dong'
}
hello() {
return 'Hello, I' + this.name
}
}
const dong = new Dong()
const obj = {
name: 'obj',
}
dong.hello.call(obj)
// Hello, I obj
这里使用到了 call 也可以使用 apply 来去改变类中方法的 this
如果不想要外部获取可以这样设置
class Dong {
name: string
constructor() {
this.name = 'dong'
}
hello(this: Dong) {
return 'Hello, I' + this.name
}
}
提取函数的 this
type GetThisParamter<T extends Function> = T extends (this: infer Target, ...args: unknown[]) => unknown ? Target : never
1、提取构造器的返回类型
interface Person {
name: 'Helllow'
}
interface PersonConstructor {
new (name: string): Person
}
type GetFuncType<T extends new (...args: any[]) => any> = T extends new (...args: any[]) => infer Return ? Return : never
type FuncReturnValueType = GetFuncType<PersonConstructor>
2、提取构造器的参数
type GetFuncConstrctorParamter<T extends new (...args: any[]) => any> = T extends new (...args: infer ArgsType) => any ? ArgsType : never
type GetConParamter = GetFuncConstrctorParamter<PersonConstructor>
这里使用到的例子是提取 React 中的 ref
type GetRefProps<Props> = 'ref' extends keyof Props ? (Props extends { ref?: infer Value | undefined } ? Value : never) : never
// type GetRefValue = GetRefProps<{ref?: undefined, name: "dong"}>
type GetRefValue = GetRefProps<{ ref?: 1; name: 'dong' }>
ts 提供了三个不能被重新赋值的变量 infer
type
类型参数
由于这三者的不可变性所以产生新类型就要 重新构造
1、扩展 元组的数量
type arr = [string, string]
type Push<Arr extends unknown[], Ele> = [...Arr, Ele]
type TupleArray = Push<arr, number>
// [string,string, number]
数组和元组的区别:数组是指同一个类型的元素构成比如 number[]
string[]
而元组的数量是固定的 类型可以由多个元素构成 比如:[number, string boolean]
1、合并两元素的元组
type tuple1 = [1, 2]
type tuple2 = ['guang', 'dong']
// 合并成以下元组
type tuple = [[1, 2], ['guang', 'dong']]
// 简单实现
type zipArr<T, Y> = T extends [infer left, infer right]
? Y extends [infer Yleft, infer Yright]
? [[left, Yleft], [right, Yright]]
: []
: []
type Value = zipArr<tuple1, tuple2>
扩展到任意个元素
type AddArray<T extends unknown[], Y extends unknown[]> = T extends [infer left, ...infer right]
? Y extends [infer Yleft, ...infer Yright]
? [[left, Yleft], ...AddArray<right, Yright>]
: []
: []
type AddArrayValue = AddArray<[1, 2, 3, 4], ['a', 'b', 'c', 'd']>
1、获得一个字符串字面量类型的 'guang' 转为首字母大写的 'Guang'。
type UpercaseStr<Str extends string> = Str extends `${infer firststring}${infer Rest}` ? `${Uppercase<firststring>}${Rest}` : never
type FirstStr = UpercaseStr<'guang'>
// Guang
2、实现 dong_dong_dong 到 dongDongDong 的变换。
type TransFromStr<T extends string> = T extends `${infer first}_${infer Tarfet}${infer Rest}`
? `${first}${Uppercase<Tarfet>}${TransFromStr<Rest>}`
: T
type transFromValue = TransFromStr<'dong_dong_dong'>
// type transFromValue = "dongDongDong"
3、删除某段字符
"dong~~~", "~" ==> "dong"
实现
type DelateStrValue<T extends string, Tager extends string> = T extends `${infer First}${Tager}${infer Last}`
? `${First}${DelateStrValue<Last, Tager>}`
: T
type DeleteStr = DelateStrValue<'dong~~~', '~'>
比如在已有的函数类型上添加一个参数
type AddArgumentsFunc<T extends Function, arg> = T extends (...args: infer Arrs) => infer Return ? (...args: [...Arrs, arg]) => Return : T
type AddValue = AddArgumentsFunc<(name: string) => boolean, number>
// (args_0: string, args_1: number) => boolean
总结 最重要的就是要选对合适的 占位符
我将 extends
后面的 ?
前面的一坨称之为 占位符
用来表示传入的参数的大致形状 然后通过处理它来来获得想要的类型比如我有这样一段代码
type TransFromStr<T extends string> = T extends `${infer first}_${infer Tarfet}${infer Rest}`
? `${first}${Uppercase<Tarfet>}${TransFromStr<Rest>}`
: T
// 首先看这行看看这个闯入的字面量的值
type transFromValue = TransFromStr<'dong_dong_dong'>
你会发现字面量为 dong_dong_dong
我们在 extends
后的占位符 ${infer first}_${infer Target}${infer Rest}
中将 _
符号前和符号后的使用 infer 声明的变量表示 后面到时候递归处理 我这里只用处理 Target
在将其三者合并就能得到想要的值。 这就是模式匹配的核心
总结下来就三点:
1、收窄类型------------- 这里主要是让传入的参数是特定的类型避免传错参数
2、选对合适的占位符
---- 占位符的意思是我编的 他就代表了你传入参数的抽象形态
3、组合----------------- 将其组合在一起返回
实例:
按着我的这个方法去做题试试
1、合并两元素的元组
type tuple1 = [1, 2]
type tuple2 = ['guang', 'dong']
// 合并成以下元组
type tuple = [[1, 2], ['guang', 'dong']]
1、先收窄类型
type zipArr<T extends unknown[], Y extends unknown[]>
2、合适的 占位符
和 3、组合
type zipArr<T extends unknown[], Y extends unknown[]> = T extends [infer left, infer right]
? Y extends [infer Yleft, infer Yright]
? [[left, Yleft], [right, Yright]]
: []
: []
最后的结果
// 最后
type zipArr<T extends unknown[], Y extends unknown[]> = T extends [infer left, infer right]
? Y extends [infer Yleft, infer Yright]
? [[left, Yleft], [right, Yright]]
: []
: []
type Value = zipArr<tuple1, tuple2>
看不懂也没事 先记下来那些没弄懂 看看文档 在刷刷题
1、Mapping
type Mapping<Obj extends object> = {
[key in keyof Obj]: [Obj[key], Obj[key], Obj[key]]
}
2、重映射 UppercaseKey
type UppercasseKey<Obj extends object> = {
[key in keyof Obje as Uppercase<key & string>: Obje[key]]
}
3、Record
// 可以简单的看成这样
type Record<K extends string | number | symbol, T> = { [P in K]: T }
指定索引和值的类型分别为 K 和 T,就可以创建一个对应的索引类型。
type UppercaseKey<Obj extends Record<string, any>> = {
[key in keyof Obj as Uppercase<key & string>]: Obj[key]
}
这里就限制 Obj 的 key 为 string
类型 值为任意类型
。。。
过滤属性的操作
type fileobjeVaule<T extends Record<string, any>, Value> = {
[key in keyof T as Value extends T[key] ? key : never]: T[key]
}
type FilterByValueType<Obj extends Record<string, any>, ValueType> = {
[Key in keyof Obj as ValueType extends Obj[Key] ? Key : never]: Obj[Key]
}
type GetFillObjValue = fileobjeVaule<obj, number>
重新构造这一块的代码
3、递归复用
为什么采用递归?TypeScript 类型系统不支持循环,但支持递归。所以只能使用递归
递归就是将问题细化为一系列相似的问题然后通过不停的调用自身去解决这些问题,满足任务的结束条件就停止。
例如解析一个嵌套的 Promise
对象
type DeepPromise<P extends Promise<unknown>> = P extends Promise<infer Value> ? Value extends Promise<unknown> : Value
: never;
type ValuePromise = DeepPromise<Promise<Promise<Promise<"Value">>>>
// Value
简化
type DeepPromise<P> = P extends Promise<infer Value> ? DeepPromise<Value> : P
1、固定元组类型的颠倒
type arr = [1, 2, 3, 4, 5]
type strat_arr = [5, 4, 3, 2, 1]
type StrotArray<T extends Array<unknown>> = T extends [infer one, infer two, infer st, infer four, infer fi] ? [fi, four, st, two, one] : T
type Value = StrotArray<arr>
当数组的长度不固定的时候就需要递归了
type arr = [1, 2, 3, 4, 5]
type StortArrayValue<T> = T extends [...infer Rest, infer last] ? [last, ...StortArrayValue<Rest>] : T
type DeepValue = StortArrayValue<arr>
2、查找引用 [1, 2, 3, 4, 5] 中是否存在 4
type TargetArray = [1, 2, 3, 4, 5]
type IncludeValueType<Arr extends unknown[], TargetValue> = Arr extends [infer first, ...infer Rest]
? first extends TargetValue
? [first, true]
: IncludeValueType<Rest, TargetValue>
: -1
type GetValue = IncludeValueType<TargetArray, 4>
3、删除数组
采取的也是递归遍历删除。
type TargetArray = [1, 2, 3, 4, 5];
type IsValue<Target, Value> = (Target extends Value? true : false)&(Value extends Target ? true : false);
type DeleteArray<Arr extends unknown[], ValueType, Result extends unknown[] = []> = Arr extends [infer First, ...infer Rest] ?
IsValue<First, ValueType> extends true ?
DeleteArray<Rest, ValueType, Result>:
DeleteArray<Rest, ValueType, [...Result, First]>
: Result
type TargetValue = DeleteArray<TargetArray, 2>
4、构造数组
构造数组类型 当数组指定长度没有指定类型的时候就要去遍历
type BuildArr<Length extends number, Ele = unknown, Result extends unknown[] = []> = Result['length'] extends Length
? Result
: BuildArr<Length, Ele, [...Result, Ele]>
type UnknownArrVaule = BuildArr<5>
//[unknown, unknown, unknown, unknown, unknown]
type NumberArrVaule = BuildArr<5, number>
//[number, number, number, number, number]
5、字符串的类型递归
当要考虑到处理值中所有的类型的时候就要考虑到使用递归
// 字符串的类型递归
type DeepString<Str extends string, From extends string, Target extends string> = Str extends `${infer First}${From}${infer Last}`
? DeepString<`${First}${Target}${Last}`, From, Target>
: Str
type S = 'guang guang guang'
type ResultValue = DeepString<S, 'guang', '?'>
6、字符串
把字符串字面量类型的每个字符都提取出来组成联合类型 "dong" => "d|o|n|g";
type Stringvalue<Str extends string> = Str extends `${infer First}${infer Last}` ? First | Stringvalue<Last> : never
type Str = Stringvalue<'dong'>
// type Str = "d" | "o" | "n" | "g"
7、反转字符串
type ResolveStr<Str extends string, Value extends string = ''> = Str extends `${infer First}${infer Last}`
? ResolveStr<Last, `${First}${Value}`>
: Value
type Str0 = ResolveStr<'dong'>
// type Str0 = "gnod"
8、对象的迭代
对象属性上的操作 当对象的属性是对象且是嵌套定义的就需要递归处理
type obj = {
a: {
b: {
c: {
f: () => 'dong'
d: {
e: {
guang: string
}
}
}
}
}
}
type DeepObj<Obj> = {
readonly [key in keyof Obj]: Obj[key] extends Object ? (Obj[key] extends Function ? Obj[key] : DeepObj<Obj[key]>) : Obj[key]
}
type DeepObjReadOnly = DeepObj<obj>
因为 ts 只有类型被用到的时候才会做类型计算。所以在处理 对象的时候没有使用到属性所以没有被处理只是被 DeepObj
函数所包裹
所以可以在前面加上一段 Obj extends never ? never 或者 Obj extends any 等,让它触发计算:
type DeepObj<Obj> = Obj extends never
? never
: {
readonly [key in keyof Obj]: Obj[key] extends Object ? (Obj[key] extends Function ? Obj[key] : DeepObj<Obj[key]>) : Obj[key]
}
ts 类型没有加减乘除,但是可以通过构造出不同的数组取出 length
来完成计算,把数组的值加减乘除 转换为对数组的提取和构造。
1、加法
type BuildArray<Length extends number, Ele = unknown, Arr extends unknown[] = []> = Arr['length'] extends Length
? Arr
: BuildArray<Length, Ele, [...Arr, Ele]>
type Add<Num1 extends number, Num2 extends number> = [...BuildArray<Num1>, ...BuildArray<Num2>]['length']
type value = Add<10, 90>
2、减法
type Subtract<Num1 extends number, Num2 extends number> = BuildArray<Num1> extends [...arr1: BuildArray<Number2>, ...arr2: infer Rest]
? Rest['length']
: never
3、乘法
type Multible<Num1 extends number, Num2 extends number, Result extends unknown[] = []> = Num2 extends 0
? Result['length']
: Multible<Num1, Subtraction<Num2, 1>, [...BuildArray<Num1>, ...Result]>
4、除法
type Divide<Num1 extends number, Num2 extends number, Result extends unknown[] = []> = Num1 extends 0
? Result['length']
: Divide<Subtraction<Num1, Num2>, Num2, [unknown, ...Result]>
案例;
5、利用数组的长度来确定字符串的长度
type Strlen<Str extends string, Result extends unknown[] = []> = Str extends `${string}${infer Rest}`
? Strlen<Rest, [...Result, Rest]>
: Result['length']
6、比较
如果 A 先到了就是 B 大 否则 A 大
// 这里默认为 Num1 大于 Num2
type GreaterThan<Num1 extends number, Num2 extends number, Result extends unknown[] = []> = Num1 extends Num2
? false
: Result['length'] extends Num1
? false
: Result['length'] extends Num2
? true
: GreaterThan<Num1, Num2, [...Result, unknown]>
7、斐波那契数列
// js 的写法
function FibonacciLoop(n) {
if (n < 2) return n
return fobonaciLoop(n - 1) + fobonaciLoop(n - 2)
}
这个属实没看明白
type FibonacciLoop<
PrevArr extends unknown[],
CurrentArr extends unknown[],
IndexArr extends unknown[] = [],
Num extends number = 1
> = IndexArr['length'] extends Num
? CurrentArr['length']
: FibonacciLoop<CurrentArr, [...PrevArr, ...CurrentArr], [...IndexArr, unknown], Num>
type Fibonacci<Num extends number> = FibonacciLoop<[1], [], [], Num>
将这个 CurrentArr
里面的内容查看就可以发现 其实就是来回的加 第一次就是 [1]
然后第二次就是和 [1,1]
第三次就是 [1,1,1]
第四次就是 第二次和第三次的合并值 [1,1] + [1,1,1]
总结: ts 类型没有办法处理数值的加减可以使用 数组的长度来完成这个办法,缺陷是没有浮点数的计算。
当类型参数为联合类型,并且在条件类型左边直接引用该类型参数的时候,TypeScript 会把每一个元素单独传入来做类型运算,最后再合并成联合类型,这种语法叫做分布式条件类型。
type Union = 'a' | 'b' | 'c'
type UppercaseA<Item extends string> = Item extends 'a' ? Uppercase<Item> : Item
// type value = "b" | "c" | "A"
1、提取字符串中的字符,首字母大写以后重新构造一个新的 aa_aa_aa => aaAaAa
之前的方法
type CamelcaseResult = 'aa_aa_aa'
type Camelcase<Str extends string> = Str extends `${infer Left}_${infer Right}${infer Rest}`
? `${Left}${Uppercase<Right>}${Camelcase<Rest>}`
: Str
type CamelcaseArr<Arr extends unknown[]> = Arr extends [infer Item, ...infer RestArr]
? [Camelcase<Item & string>, ...CamelcaseArr<RestArr>]
: []
type value = CamelcaseArr<['aa_aa_aa', 'ff_ff']>
// type value = ["aaAaAa", "ffFf"]
如果是联合类型的呢 ts 会将联合类型中的每一个元素都会传入做一次单独的计算然后再组合成联合类型
type CamelcaseUnion<Item extends string> = Item extends `${infer Left}_${infer Right}${infer Rest}`
? `${Left}${Uppercase<Right>}${CamelcaseUnion<Rest>}`
: Item
type value = CamelcaseUnion<'aa_aa_aa' | 'bb_bb_bb' | 'ff_ff_ff'>
2、判断联合类型
type IsUnion<A, B = A> = A extends A ? ([B] extends [A] ? false : true) : never
type value = IsUnion<'a' | 'b' | 'c'>
传入联合类型会返回 true
传入其他类型会返回 false
看这里的案例 IsUnion<A, B = A>
在条件类型中 左边是联合类型, 会把每个元素传入做计算,右边不会。A extends A ?
所以这里的 A 就代表是 联合类型 extends 中 的右侧 A 是标识的联合类型 左侧的值代表 联合类型中的一部份 也就是说 A extends A
就是为了触发分布式条件类型, 让 A 的类型单独传入; 而 B 的类型将会作为整个 A 的类型传入。
当 A 是联合类型时:
1、A extends A 这种写法是为了触发分布式条件类型,让每个类型单独传入处理的,没别的意义。
2、A extends A 和 [A] extends [A] 是不同的处理,前者是单个类型和整个类型做判断,后者两边都是整个联合类型,因为只有 extends 左边直接是类型参数才会触发分布式条件类型。
特别的: 数组转联合类型
type aa = ['a', 'b', 'c'][number]
// type aa = "a" | "b" | "c"
希望传入 'A' | 'B' 的时候,能够返回所有的组合: 'A' | 'B' | 'BA' | 'AB'。
type Combination<A extends string, B extends string = A> = A extends A ? Combination<A, AllCombinations<Exclude<B, A>>> : never
Exclude<B, A>
就是 B 中排除 A 的值 AllCombinations<X,Y>
这里就代表了 x 和 Y 组合的所有类型
1、any
于任何一个类型交叉都是 any
根据这一特性可以判断类型是否是 any
type IsAny<T> = 'any' extends 'yes' & T ? true : false
type Value = IsAny<any>
// true
type Value_1 = IssAny<number>
// false
2、IsEqual
判断两者是否相同
type IsEqual<A, B> = (A extends B ? true : false) & (B extends A ? true : false)
type IsEqualValue = IsEqual<'a', any>
// 这样就会出现问题
any 可以是任何类型,类型也可以是 any ,所以就会出现问题
使用这样的方式可以正确判断
type IsEqual2<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false
3、IsUnion
type IsUnion<A, B = A> = A extends A ? ([B] extends [A] ? false : true) : never
4、IsNever
type IsNever<T> = [T] extends [never] ? true : false
5、any 在合并类型中会返回 TrueType 和 falseType 的合值
type TestAny<T> = T extends number ? 1 : 2
type value = TestAny<any>
// '1'|'2'
6、数组和元组
元组类型也是数组类型,但每个元素都是只读的,并且 length 是数字字面量,而数组的 length 是 number。
type NotEqual<A, B> = (<T>() => extends A ? 1 : 2) extends (<T>() => extends B ? 1 : 2) ? false : true;
type IsTuple<T> = T extends readonly [...params: infer Eles] ? NotEqual<Eles['length'], number> : false;
T extends [...params: infer Eles]
这段是判断 T 是一个只读数组
NotEqual
用于判断两者不相等的时候是元组
类型之间是有父子关系的,更具体的那个是子类型,比如 A 和 B 的交叉类型 A & B 就是联合类型 A | B 的子类型,因为更具体。
如果允许父类型赋值给子类型,就叫做逆变
。
如果允许子类型赋值给父类型,就叫做协变
。
ts 中函数的参数具有逆变性质,参数有可能是多类型的,参数类型会变成他们的交叉类型。
type UnionToIntersection<U> = (U extends U ? (x: U) => unknown : never) extends (x: infer R) => unknown ? R : never
type s = UnionToIntersection<{ a: 1 } | { b: 2 }>
// {a: 1;} & {b: 2;}
这里就是利用到了函数参数的逆变性质,可以将参数类型变成交叉类型
U extends U ?
为了触发联合类型的分发机制,将每个类型单独取出用于传入函数的参数中 函数的参数是多种类型所以就会变成交叉类型 返回的 R 是函数的参数做出的结果
比如像这种就会产出合并的结果
type Fun<T> = ((x: T) => unknown) extends (x: infer R) => unknown ? R : never
type ss = Fun<{ a: 1 } | { b: 2 }>
// {a: 1;} | {b: 2;}