持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第 8 天
体操基本原理
// 当我们想写
if( A <= B ) true else false
// 那么可以这样写
type A = 1
type B = 1 | 2
type Result = A extends B ? true : false
// 如果 if else 复杂一点 比如说有两个条件呢?
if( A <= B ) and ( C <= D ) ...
// 那么可以这样写
type A = 1
type B = 1 | 2
type C = 3
type D = 3 | 4
type Result = A extends B
? C extends D
? "true, true"
: "true, false"
: C extends D
? "false, true"
: "false, false"
空元组
数组就是长度无限的
元组就是长度有限制
// 注意:这里的数组是类型,不是 JS 的值,一般把这种叫做 元组
type A = []
// 判断一个元组是不是空元组
type IsEmptyArray<Arr extends unknown[]> =
Arr['length'] extends 0 ? true : false
type Result = IsEmptyArray<A>
// ^-- true
非空元组
type A = [1]
type NotEmpty<Arr extends unknown[]> =
Arr extends [...infer X, infer Last] ? true : false
type Result = NotEmpty<A>
// ^-- true
Arr extends [...infer X, infer Last] ? true : false
也可以写成
Arr extends [...unknown[], unknown] ? true : false
那为什么要加 infer ?
因为有的时候我们可能不想知道它到底是什么,不想写类型。
就用变量表示,可以吗?
Arr extends [...X, Y] ? true : false 但是这样写就会报错
这个时候如果加上 infer
Arr extends [...infer X, infer Y] ? true : false
那 TS 就知道我写的是类型,只是不想告诉 TS 这个是什么。这里的 X 其实引用了 X = unknown[] 就是给 unknown[] 数组取一个名字为 X,这个 X 可以为空,Y 其实也是给 unknown 取一个名字。
递归
type A = ['ji', 'ni', 'td', 'mw']
type Reverse<Arr extends unknown[]> =
Arr extends [...infer Rest, infer Last]
? [Last, ...Reverse<Rest>]
: Arr
type Result = Reverse<A>
// Result is ['mw', 'td', 'ni', 'ji']
模式匹配 + infer"引用"
type Tuple = ['ji', 'ni', 'td', 'mw'] // 这里面的都是类型,不是元素
type Result1 = Tuple extends [infer First, ...infer Rest]
// 这个可以写成 type Result1 = Tuple extends [infer First, ...string[]]
? First : never
// Result1 is 'ji'
type Result2 = Tuple extends [infer First, ...infer Rest]
// 可以写成 type Result2 = Tuple extends ['ji', ...infer Rest]
? Rest : never
// Result2 is ['ni', 'td', 'mw']
模式匹配:我用我这个模式去匹配你那个模式,如果两个匹配上了,那它就自动赋值。
如果不写 infer, 就会提示 cannot find name 'First', 所以可以认为 infer 其实就是 var,也可以认为 infer 其实就是引用,这个 First 它引用的是当前元素的这个类型。
获取元组的长度
type A = ['ji', 'ni', 'td', 'mw']
type R = A['length'] // 没有报错
// 因为这是 JS 的特性
元组的基本体操
// 把一个元组变的长度更长
type A = [1] // 有的时候 A 里面 类型很长/类型很复杂
type B = [...A, 2] // 复制的时候就会觉得好长不想复制,于是就用 ... 把它弄下来
type B = [1, 2]
type C = [3, 4]
type D = [...B, ...C]
type D = [1, 2, 3, 4]
// 注意 1, 2, 3, 4 都是类型, 不是值
type Last<T> = T extends [...items: unknown[], last: infer X]
? X
: never
type E = Last<D> // 获取的是最后一项
// ^--- E = 4 注意: 这里的 4 是类型,不是值
// 一个错误的写法
type Last<T extends unknown[]> = T[T['length'] - 1] // TS 并没有提供 - 1 的操作
// 如果想获取除了最后一项的其他项
type D = [1, 2, 3, 4]
// 注意 1, 2, 3, 4 都是类型, 不是值
type NoLast<T> = T extends [...infer X, unknown]
? X
: never
type E = NoLast<D> // 获取的是最后一项
// ^--- E = [1, 2, 3] 注意: 这里的 1, 2, 3 是类型,不是值
字符串的基本体操
内置的 intrinsic
type A = 'hone'
type B = Capitalize<A> // 这个 Capitalize 是 TS 已经帮我们写好的
// ^-- type B = 'Hone'
type C = 'ji' | 'ni' | 'td' | 'mw'
type X = Capitalize<C>
// ^-- type X = 'Ji' | 'Ni' | 'Td' | 'Mw'
intrinsic 的意思是这玩意我已经内置了,你不用管它是怎么实现的(内在的、固有的)。
哪些是内置的 intrinsic:
- Uppercase 全变成大写
- Lowercase 全部变小写
- Capitalize 首字母大写
- Uncapitalize 首字母小写
使用以上内置的 intrinsic,类型的顺序可能会变,因为联合类型它就是没有顺序的,元组是有顺序的。
模版字符串
// 注意: 以下写法都是类型,不是值
type A = 'ji'
type B = 'ni'
type C = 'td'
type D = 'mw'
type X = `${A} ${B} ${C} ${D}`
// ^-- type X = 'ji ni td mw'
获取字符串的第一个
// 获取第一个
type A = 'ji ni td mw'
// 这个 ${infer F} 永远是第一个, ${string} 永远是除第一个外
type First<T extends string> = T extends `${infer F}${string}` ? F : never
type Result = First<A>
// ^-- 'j'
// 如果 ${infer F}${string} 写成 ${string}${string} 就不知道如何分配了
// 有可能匹配到 '' ‘ji ni td mw’
// 也有可能匹配到 ‘ji ni td m’ ‘w‘
获取字符串的最后一个
// 如何获取字符串的最后一个
// 我们可以获取元组的最后一项
type LastOfTuple<T extends unknown[]> =
T extends [...infer _, infer L] ? L : never
// 字符串可以转为元组
type StringToTuple<S extends string> =
S extends `${infer F} ${infer R}`
? [F, ...StringToTuple<R>]
: []
// 我们可以获取字符串的最后一项
type LastOfString<S extends string> =
LastOfTuple<StringToTuple<S>>
type R = LastOfTuple<"ji ni td mw">
infer 的文档在哪里
type Flatten<Type> = Type extends Array<infer Item> ? Item : Type
Here, we used the
inferkeyword to declaratively introduce a new generic type variable namedIteminstead of specifying how to retrieve the element type ofTwithin the true branch. This frees us from having to think about how to dig through and probing apart the structure of the types we’re interested in.
大概意思: 使用 infer 关键字来显式的声明一个新的泛型变量用来代替之前的类型。你可以直接写一个 infer Item 你就不用管这个 Item 是 number 还是 string 还是 unknown , 你直接用一个变量来代替它,不管它是什么,我就原封不动的返回。
infer 文档地址: 链接🔗
type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;
type X = Flatten<string[]> // 获取到 string[] 里面 string
type Y = Flatten<Array<number>> // 获取到 Array 里面的 number
// Array 里面有什么东西,它就可以被 Item 引用
如何自己搜到 infer 的定义
直接搜?
每个结果点进去 cmd/ctrl + f 全局搜索 发现并没有找到与 infer 相关的。
我们要搜 Conditional Types,点进去然后搜 infer
根据文档的排版说明: 暗示 infer 通常是出现在 extends 里面的。
TS 递归的层数限制
当前场景根据测试最多递归 48层。
把每一个 key 都加上 Readonly。
interface SomeObject {
a: {
b: {
c: number
}
}
}
const obj: Readonly<SomeObject> = { a: { b: { c: 2 } } }
// 所以对 a 进行赋值就会报错
字符串转元组、转联合
// 字符串转元组
type StringToTuple<S extends string> =
S extends `${infer First} ${infer Rest}`
? [First, ...StringToTuple<Rest>]
: []
type Result = StringToTuple<'jinitdmw'>
// type Result = ['j', 'i', 'n', 'i', 't', 'd', 'm', 'w']
// 字符串转联合
// never 和任何类型联合起来是不影响结果的
type StringToUnion<S extends string> =
S extends `${infer First}${infer Rest}`
? First | StringToUnion<Rest>
: never
type Result = StringToUnion<'jinitdmw'>
// type Result = 'j' | 'i' | 'n' | 't' | 'd' | 'm' | 'w'
// 注意,联合类型自动去重了