{"id":18321663,"url":"https://github.com/bryanadamss/type-challenges-analysis","last_synced_at":"2025-07-19T19:39:26.106Z","repository":{"id":45836848,"uuid":"497020365","full_name":"BryanAdamss/type-challenges-analysis","owner":"BryanAdamss","description":"类型体操基础知识点总结 type-challenges-analysis","archived":false,"fork":false,"pushed_at":"2024-03-16T07:23:17.000Z","size":449,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-15T08:33:09.880Z","etag":null,"topics":["typescript","typescript-challenges"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/BryanAdamss.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2022-05-27T14:07:01.000Z","updated_at":"2024-01-05T15:47:29.000Z","dependencies_parsed_at":"2024-03-16T08:38:55.105Z","dependency_job_id":null,"html_url":"https://github.com/BryanAdamss/type-challenges-analysis","commit_stats":null,"previous_names":["bryanadamss/type-challenges-analysis"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BryanAdamss%2Ftype-challenges-analysis","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BryanAdamss%2Ftype-challenges-analysis/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BryanAdamss%2Ftype-challenges-analysis/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BryanAdamss%2Ftype-challenges-analysis/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/BryanAdamss","download_url":"https://codeload.github.com/BryanAdamss/type-challenges-analysis/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248055309,"owners_count":21040151,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["typescript","typescript-challenges"],"created_at":"2024-11-05T18:20:39.966Z","updated_at":"2025-04-09T14:34:41.573Z","avatar_url":"https://github.com/BryanAdamss.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n# 类型体操基础知识点总结\n\n- 目录TOC\n\t- [TS 类型和集合理论基础](#ts-类型和集合理论基础)\n\t- [类型体操技巧](#类型体操技巧)\n\t- [内置工具类型](#内置工具类型)\n\t- [参考](#参考)\n\n## TS 类型和集合理论基础\n\n- 集合论是TS类型的一个理论基础\n- 每个类型都代表一类有相同特征的值\n\t- 例如`number`类型是一个无限的集合,代表所有的数字`string`和`object`同理\n\t- 除了无限集合还有一些有限集合,例如`boolean`/`null`/`undefined`/字符串常量/字符串常量联合类型都是有限集合,它们只包含有限个元素\n\n```ts\n// both true, string literal ⊂ string\ntype W = 'a' extends string ? true : false;\ntype X = 'a' | 'b' extends string ? true : false;\n\n// true, string literal ⊆ same string literal\ntype Y = 'a' extends 'a' ? true : false;\n\n// true, string ⊆ string\ntype Z = string extends string ? true : false;\n```\n\n- `unknown`/`any`/`never`\n\t- `unknown`是`top type`,类似集合中的全集`U`,包含所有可能值\n\t\t- 代表一个暂时未知的类型,它是在类型系统中明确定义的\n\t\t- 可以把任意值赋给`unknown`\n\t\t- 但不能把`unknown`直接赋值给其它类型(unknown/any 除外)\n\t\t\t- 需要先通过`typeof`或`type assert`确定类型再赋值\n\t- `any`代表这是一个任意值,更像一个 ts 指令,告诉 ts 禁用类型系统\n\t\t- 可以赋值给任意类型\n\t\t- 也可以被任意类型值赋值\n\t- `never`是一个`bottom type`,类似集合中的空集\n\t\t- 它只能接受`never`(any/unknown 都无法赋值给它)\n\n```ts\nlet vAny: any = 'any1'\nlet vAny2:any = 'any2'\n\nlet vUnknown:unknown = 'unknown1'\nlet vUnknown2:unknown='unknown2'\n\nlet n1:number = 1\nlet s1:string ='hello'\n\n/** never 无法被赋值,但它能赋值给其它任意类型 */\nlet nv:never = 'never' // error\n\n/** 任意值(包括 unknown和 any 自身)都可以赋值给any */\nvAny = n1\nvAny = s1\nvAny = vAny2\nvAny = vUnknown\nvAny = nv\n\n/** any也可以赋值给任意类型 */\nn1 = vAny\ns1 = vAny\nvUnknown = vAny\n\n/** unknown 和 any一样,任意值都可以赋值给unknown */\nvUnknown = n1\nvUnknown = s1\nvUnknown = vAny\nvUnknown = vUnknown2\n\n/** 但unknown无法赋值给其它类型,除了 any 和 unknown */\nn1 = vUnknown2\nvAny = vUnknown2\nvUnknown = vUnknown2\n```\n\n![image](https://github.com/BryanAdamss/tdl-ts/assets/7441504/7783f5b3-1feb-4eb4-8c2b-50e96a10b755)\n\n- 集合的运算\n\t- `|`和`\u0026`\n\t\t- 当类型为非对象类型时,`|`类似取并集(union),`\u0026`类似取交集(intersection)\n\t\t- ![image](https://github.com/BryanAdamss/tdl-ts/assets/7441504/72596bb2-637d-430a-900e-efe4317a71ba)\n\t\t- 而如果是对象类型,则有点相反`|`行为类似取交集,能用的方法或属性是二者共有的,`\u0026`则类似取并集,能用的方法是二者相加的\n\t\t- 合并类型(取并集)的最佳实践\n\t\t\t- 非对象类型用`|`取并集\n\t\t\t- 对象类型用`\u0026`取并集\n\n```ts\ntype StringOrNumber = string | number; // 类似string + number ,string 或 number\ntype StringAndNumber = string \u0026 number; // never,二者不会有交集,所以 never\n\n/** 对象或 interface 在用|或\u0026时表现有点相反 */\ninterface ICat {\n  eat(): void;\n  meow(): void;\n}\n\ninterface IDog {\n  eat(): void;\n  bark(): void;\n}\n\ndeclare function Pet(): ICat | IDog; \n\nconst pet = new Pet();\n\npet.eat(); // 成功,只能用二者共有的方法\npet.meow(); // fails\npet.bark(); // fails\n\n\ndeclare function AnotherPet(): ICat \u0026 IDog;\n\nconst anotherPet = new AnotherPet();\n\nanotherPet.eat(); // 成功,类似取了并集\nanotherPet.meow(); // succeeds\nanotherPet.bark(); // succeeds\n```\n\n## 类型体操技巧\n\n#### 用 `T[P]`获取类型\n\n```ts\ntype Person = { age: number; name: string; alive: boolean };\n\ntype Age = Person[\"age\"]; // number\n```\n\n#### 用`keyof`获取对象 key 组成的联合类型\n\n```ts\ntype Obj = {\n\thello:string\n\ttest:number\n}\n\ntype ObjKeys = keyof Obj // 'hello' | 'test'\n```\n\n### 用`keyof`获取数组索引组成的类型\n\n```ts\n// 数组本质是一个以索引为key的对象\n// {0:'hello',1:'world'}\nconst ff = ['hello', 'world'] as const\nconst ff2 = ['hello','world']\n\ntype ffr\u003cT\u003e = { [p in keyof T]: p }\n\ntype ffrr = ffr\u003ctypeof ff\u003e // readonly [\"0\",\"1\"]\ntype ffrr2 = ffr\u003ctypeof ff2\u003e // number[]\n```\n\n### 在对象中用`in`遍历联合类型\n\n```ts\ntype Obj = {\n\thello:string\n\ttest:number\n}\n\ntype ObjKeys = keyof Obj // 'hello' | 'test'\n\ntype NewType = {\n\t[Key in ObjKeys]:any\n}\n\n// `in`操作符的右侧通常跟一个联合类型，可以使用`in`来迭代这个联合类型\n// 仅演示使用, K为每次迭代的项\nK in 'name' | 'age' | 'sex'\nK = 'name' // 第一次迭代结果\nK = 'age'  // 第二次迭代结果\nK = 'sex'  // 第三次迭代结果\n```\n\n### 用`T[number]`获取数组值组成的联合类型并用`in`遍历\n\n```ts\nconst ff = ['hello', 'world'] as const\n\n// 遍历数组应当使用 p in T[number]\ntype fffr\u003cT extends readonly any[]\u003e = { [p in T[number]]: p }\ntype fffrr = fffr\u003ctypeof ff\u003e // {  hello: \"hello\";world: \"world\"; }\n```\n\n### 用`typeof`获取值空间的类型\n\n```ts\nconst a = [1,2,3] // 值空间\ntype e = typeof a // number[]\n\nconst f = [1, 2, 'me'] as const\ntype g = typeof f // readonly [1,2,'me']\n```\n\n### 用`const`或`as const`做类型收缩\n\n```ts\n// const 类型字面量会自动收缩\nlet str = '123' // 类型为string\nconst strConst = '123' // 类型为'\"123\"' ，const意味着值不能被修改，值不能被修改，类型也不会变化，所以它的类型就固化收缩为\"123\"字\n\n// 用于将字面量值断言为const，不能被修改\n// 使用后，字面量值的类型会进一步收缩\n// 对象字面量属性会添加上readonly修饰符\n// 数组会转变为只读元组\nlet x = 'hello'\ntype xr = typeof x // string\n\nlet y = 'hello' as const\ntype yr = typeof y // '\"hello\"' ，从string 收缩为 '\"hello\"'\n\nlet aa = [1, 2, 3] as const\ntype aar = typeof aa // readonly [1,2,3]\n\nlet bb = { test: 1, hello: 'world' }\ntype bbr = typeof bb //{test:number;hello:string;}\n\n// 可用于let声明\nlet cc = { test: 1, hello: 'world' } as const\ntype ccr = typeof cc // { readonly test: 1; readonly hello: \"world\"; }\n\n// 可用于var声明\nvar dd = { test: 1, hello: 'world' } as const\ntype ddr = typeof dd // { readonly test: 1; readonly hello: \"world\"; }\n```\n\n### 用`+`和`-`做属性添加删除\n\n```ts\ntype Required\u003cT\u003e = {\n  [P in keyof T]-?: T[P] // 去除?\n}\ntype Person = {\n  name: string;\n  age?: number;\n}\n\n// 结果：{ name: string; age: number; }\ntype result = Required\u003cPerson\u003e\n```\n\n### 用`extends`做类型约束\n\n```ts\n// 类型约束,U 必须为联合类型 T 的子集\nU extends keyof T\n\n// T 要是 string[]的子集\nfunction Test\u003cT extends string[]\u003e(arg1:T){}\n```\n\n### 用`extends`做条件分支\n\n```ts\n// 基本形式,类似三元表达式\n// T 是否是 U 一个子集\nT extends U ? 'Y' : 'N'\n\ntype result1 = true extends boolean ? true : false                    // true\ntype result2 = 'name' extends 'name' | 'age' ? true : false           // true\ntype result3 = [1, 2, 3] extends { length: number; } ? true : false   // true\ntype result4 = [1, 2, 3] extends Array\u003cnumber\u003e ? true : false         // true\n```\n\n### 分布式条件类型\n\n```ts\n// 当用 extends 做条件分支时,若左右有一个为联合类型时,会触发分布式条件类型,类似数学中的分配律\n\n// 内置工具：交集\ntype Extract\u003cT, U\u003e = T extends U ? T : never;\ntype type1 = 'name'|'age'\ntype type2 = 'name'|'address'|'sex'\n\n// 交集结果：'name'\ntype result = Extract\u003ctype1, type2\u003e\n\n// 推理步骤\n'name'|'age' extends 'name'|'address'|'sex' ? T : never\nstep1： ('name' extends 'name'|'address'|'sex' ? 'name' : never) =\u003e 'name'\nstep2:  ('age' extends 'name'|'address'|'sex' ? 'age' : never)   =\u003e never\nresult: 'name' | never =\u003e 'name'\n```\n\n### 用`infer`做推断占位\n\n```ts\n// infer 本质是延迟推导,可做推导占位用,等到真正推导成功后，它能准确的返回正确的类型\n\ntype ReturnType\u003cT\u003e = T extends (...args: any) =\u003e infer R ? R : never\n\nconst add = (a: number, b: number): number =\u003e {\n  return a + b\n}\n\n// 结果: number\ntype result = ReturnType\u003ctypeof add\u003e\n```\n\n### 用解构语法和 reset 操作符获取数组首个元素\n\n```ts\ntype First\u003cT extends any[]\u003e = T extends [/** 首个 */infer Fir, /** reset 操作符代表后续类型全部暂存到 Res 中 */...infer Res] ? Fir : n\n```\n\n### 判断是否 never 的固定范式\n\n```ts\ntype MyIsNever\u003cT\u003e = [T] extends [never] ? true : false\n\n// 为何不能用 T extends never\ntype MyIsNever2\u003cT\u003e = T extends never ? true : false\ntype A = MyIsNever2\u003c'str'\u003e //str\ntype B = MyIsNever2\u003cnever\u003e // never 因为MyIsNever2\u003cnever\u003e 中的 never 实际上是一个空的联合类型，一项都没有，所以 T extends ... 过程实际上被整体跳过了，所以最后的结果就是 never。\n```\n\n### 关闭分布式条件类型的方法\n\n- 在要判断的联合类型上加上`[]`\n- https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types\n\n```ts\ntype MyIsNever\u003cT\u003e = [T] extends [never] ? true : false\n```\n\n### 遍历联合类型自身的固定范式\n\n```ts\n/** 直接遍历联合类型自身的范式,extends 自身即可 */\ntype MyItrUnion\u003cT\u003e = T extends T ? [T] : never\n\n/** 运用了分布式条件类型:在 条件类型 中使用 泛型参数 时，如果泛型参数是 联合类型，则会产生 distributive 的效果。 */\n/** https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types */\n// 'A'|'B' extends 'A'|'B'=\u003e\n// 'A' extends 'A'|'B'=\u003e ['A']\n// 'B' extends 'A'|'B'=\u003e ['B']\n// =\u003e ['A']|['B']\n\ntype C = MyItrUnion\u003c'A' | 'B'\u003e // ['A'] | ['B']\n```\n\n### 解构数组的联合类型也会产生分布式条件类型效果\n\n```ts\ntype D = [1, 2] | [3, 4]\ntype E = ['a', 'b'] | ['c', 'd']\n\ntype F = [true, ...D, ...E]\n\n// [true, 1, 2, \"a\", \"b\"] | [true, 1, 2, \"c\", \"d\"] | [true, 3, 4, \"a\", \"b\"] | [true, 3, 4, \"c\", \"d\"]\n```\n\n### 利用递归构造循环\n\n- ts中没有循环语句,需要通过递归来模拟额循环\n\n```ts\ntype LengthOfString1\u003c\n\tS extends string,\n\tT extends string[] = []\n\u003e = S extends `${infer F}${infer R}`\n\t? LengthOfString1\u003cR, [...T, F]\u003e /** 递归模拟循环 */\n\t: T['length'] \n```\n\n### 在方法中为参数的类型设置默认值\n\n- 类似js 可以给类型参数设置默认值\n\n```ts\ntype LengthOfString1\u003c\n\tS extends string,\n\tT extends string[] = [] /** 知识点1,设置临时变量存储数组,默认为[] */\n\u003e = S extends `${infer F}${infer R}`\n\t? LengthOfString1\u003cR, [...T, F]\u003e \n\t: T['length'] \n```\n\n### 利用reset 操作符号用数组中添加元素\n\n- ts 类型操作中没有 push 语法,可用 reset 操作符模拟\n\n```ts\ntype LengthOfString1\u003c\n\tS extends string,\n\tT extends string[] = [] \n\u003e = S extends `${infer F}${infer R}`\n\t? LengthOfString1\u003cR, [...T, F]\u003e /** 知识点2,利用[...T,F]向数组添加元素 */\n\t: T['length'] \n```\n\n### 涉及数字和运算,则构造数组并利用`T['length']`数组长度来做运算\n\n```ts\ntype LengthOfString1\u003c\n\tS extends string,\n\tT extends string[] = []\n\u003e = S extends `${infer F}${infer R}`\n\t? LengthOfString1\u003cR, [...T, F]\u003e\n\t: T['length'] /** 知识点3,通过将字符转为数组,获取 length 获取长度 */\n```\n\n### 用reset 操作符给数组降维\n\n```ts\ntype MyFlatten\u003cT extends unknown[], A extends unknown[] = []\u003e = T extends [\n\tinfer First,\n\t...infer Rest\n]\n\t? First extends unknown[]\n\t\t? MyFlatten\u003c\n\t\t\t[...First, ...Rest] /** 知识点 ...类似 js 中可以给数组降维 */,\n\t\t\tA\u003e\t\n\t\t: MyFlatten\u003cRest, [...A, First]\u003e\n\t: A\n```\n\n### 使用字符串模板将 number 转为 string\n\n```ts\ntype Absolute\u003cT extends number | string | bigint\u003e =\n\t`${T}` extends `-${infer R}` /** 知识点,使用字符串模板将 number 转为string */\n\t? R\n\t: `${T}`\ntype AB = -1_000_000n\ntype BC = `${AB}` // \"-1000000\"\n```\n\n### 使用模板字符串将 string 转 number\n\n```ts\n/** 知识点,字符转数字,https://devblogs.microsoft.com/typescript/announcing-typescript-4-8-beta/#improved-inference-for-infer-types-in-template-string-types */\ntype ParseInt\u003cT extends string\u003e = T extends `${infer Digit extends number}`\n\t? Digit\n\t: n\n```\n\n### 使用模板字符串将 boolean 转换为 string\n\n```ts\n/** 解法:https://github.com/type-challenges/type-challenges/issues/14094 */\ntype Flip\u003cT extends Record\u003cstring, string | number | boolean\u003e\u003e = {\n\t/** 知识点,通过模板字符串,将 boolean 转换为 string 做为key 用 */\n\t[P in keyof T as `${T[P]}`]: P\n}\n```\n\n### extends 分支中可以用联合类型操作符|直接拼接\n\n```ts\ntype StringToUnion\u003cT extends string\u003e = T extends `${infer F}${infer Rest}`\n\t? F | StringToUnion\u003cRest\u003e /** 知识点,extends语句中可以用联合直接拼接 */\n\t: never\n```\n\n### 使用内置类型转大小写\n\n```ts\ntype KebabCase\u003cS extends string\u003e = S extends `${infer S1}${infer S2}`\n\t? S2 extends Uncapitalize\u003cS2\u003e /** 知识点,Uncapitalize将字符串文字类型的第一个字符转换为小写 */\n\t\t? `${Uncapitalize\u003cS1\u003e}${KebabCase\u003cS2\u003e}`\n\t\t: `${Uncapitalize\u003cS1\u003e}-${KebabCase\u003cS2\u003e}`\n\t: S\n```\n\n### 判断两个类型相等的固定范式\n\n```ts\n// 参考https://github.com/microsoft/TypeScript/issues/27024#issuecomment-421529650\ntype Equals\u003cX, Y\u003e =\n    (\u003cT\u003e() =\u003e T extends X ? 1 : 2) extends\n    (\u003cT\u003e() =\u003e T extends Y ? 1 : 2) ? true : false;\n```\n\n### 如何判断空对象\n\n```ts\n/** 知识点,没有属性的对象不能用{}判断, 需要用{ [key: string]: never }*/\ntype t3 = { name: 'test' } extends {} ? true : false // true\ntype t4 = { name: 'test' } extends { [key: string]: never } ? true : false // false\ntype t5 = {} extends { [key: string]: never } ? true : false // true\n```\n\n### 如何判断是否联合类型\n\n- 非联合类型排除掉自身后只剩下 never,可通过此判断是否联合类型\n\n```ts\ntype IsUnion\u003cT, U = T\u003e = [T] extends [never] /** 排除 never */\n? false\n: T extends T /** 遍历联合类型 */\n? /** 知识点,非联合类型排除掉自身后只剩下 never,可通过此判断是否联合类型 */\n\t/** type CC = Exclude\u003cstring, string\u003e=\u003enever */\n\t[Exclude\u003cU, T\u003e] extends [never]\n\t? false\n\t: true\n: never\n```\n\n### 如何给 key 重新起名\n\n- 通过 as 进行 key-remapping ,在key-remapping 中可以用 extends ,infer等\n- 常用在对象 key 遍历时,对 key 重新映射,示例1\n- 文档:https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#key-remapping-via-as\n\n```ts\ntype EventConfig\u003cEvents extends { kind: string }\u003e = {\n\t/** 遍历Events并取 E['kind']做 key 名 */\n\t[E in Events as E['kind']]: (event: E) =\u003e void\n}  \n\ntype SquareEvent = { kind: 'square'; x: number; y: number }\ntype CircleEvent = { kind: 'circle'; radius: number }\n\ntype Config = EventConfig\u003cSquareEvent | CircleEvent\u003e\n// {  \n// square: (event: SquareEvent) =\u003e void;  \n// circle: (event: CircleEvent) =\u003e void;  \n// }\n```\n\n### 利用ts 中的 global type 简化书写\n\n- `PropertyKey`是 `ts` 中的`global type`,等价于`string | number | symbol`\n- `index-signature` 的类型是 `string | number | symbol`\n\n```ts\n/** 知识点,PropertyKey是ts 中的global type,等价于string | number | symbol */\ntype RemoveIndexSignature\u003cT, P = PropertyKey\u003e = {\n/** 遍历T获得 key 并 re-mapping */\n/** 知识点:index-signature 的取类型是 string | number | symbol */\n/** 参考:https://www.totaltypescript.com/concepts/propertykey-type */\n/** 如果 P(string | number | symbol) 是 Key 的子集,则说明是 index-signature,则剔除 */\n[Key in keyof T as P extends Key\n\t? never\n\t: /** 如果 Key 是 P的子集,则说明是需要保留的 Key */\n\tKey extends P\n\t? Key\n\t: never]: T[Key]\n```\n\n### 模板字符串+infer 可以实现类似正则的效果\n\n```ts\n/** 知识点,模板字符串+infer 可实现类似正则匹配效果 */\ntype CheckPercentageSign\u003cS\u003e = S extends `${infer N}%` ? [N, '%'] : [S, '']\n```\n\n### infer推断出来的类型可以当参数传递\n\n```ts\ntype CheckSign\u003cSign\u003e = Sign extends '+' | '-' ? Sign : never\n/** 知识点,模板字符串+infer 可实现类似正则匹配效果 */\ntype CheckPercentageSign\u003cS\u003e = S extends `${infer N}%` ? [N, '%'] : [S, '']\n\ntype PercentageParser\u003cA extends string\u003e = A extends `${CheckSign\u003c\n\t/** 知识点,infer的类型可当参数传递 */\n\tinfer L\n\u003e}${infer R}`\n\t? [L, ...CheckPercentageSign\u003cR\u003e]\n\t: ['', ...CheckPercentageSign\u003cA\u003e]\n```\n\n### 模板字符串中可以用 string 代表任意字符\n\n```ts\n/** 知识点,模板字符串匹配时,可不用 infer 接收,用string表明后面是 string 即可 */\ntype StartsWith\u003cT extends string, U extends string\u003e = T extends `${U}${string}`\n\t? true\n\t: f\n```\n\n### 如何让交叉类型在悬浮时直接展开\n\n```ts\n// 方法1,通过遍历展开\ntype IntersectionObj\u003cT\u003e = {\n\t[P in keyof T]: T[P]\n}\n\ntype AAAA = {test:3} \u0026 {name:4} // 悬浮A 时显示的是{ test: 3 } \u0026 { name: 4 }\n\ntype BBBB = IntersectionObj\u003cAAAA\u003e // 悬浮BBBB时,显示的则是展开的{test:3,name:4}\n\n// 方法2,递归深层次展开\ntype ExpandRecursively\u003cT\u003e = T extends object\n? T extends infer O\n\t? { [K in keyof O]: ExpandRecursively\u003cO[K]\u003e }\n\t: never\n: T\n\n// 方法3,用 Omit\u003cT,never\u003e\ntype CCCC = Omit\u003cAAAA,never\u003e // // 悬浮CCCC时,显示的是展开的{test:3,name:4}\n```\n\n### 对象每个键的值转联合类型\n\n```ts\n/** 知识点,对象转联合,用 T[keyof T] */\ntype ObjectToUnion\u003cT\u003e = T[keyof T]\n```\n\n### 数组转联合类型\n\n```ts\n/** 知识点,数组转联合类型用下标*/\n['1', '2']['number'] // '1' | '2'\n\ntype ArrToUnion\u003cT\u003e = T extends any[] ? T[number] : T\n```\n\n### 强制某类型(转换)为特定类型\n\n```ts\n/** 知识点,强制某个类型必须为 类型 A,否则原样返回,请用 T \u0026 A */\n/** 参考:https://github.com/type-challenges/type-challenges/issues/6733#issuecomment-1136127999 */\ntype MustString\u003cT\u003e = T \u0026 string // string \u0026 T 结果也是一样\n// 等价于\ntype MustStringEqual\u003cT\u003e = T extends string ? T : never\n\ntype AA = MustString\u003ctrue\u003e // never\ntype BBB = MustString\u003c'hello'\u003e // 'hello'\n```\n\n### 利用infer + | 将字符串转为联合类型\n\n```ts\n/** 知识点,利用 infer + | 将字符串转换为联合类型,\"AB\"-\u003e\"\"|\"A\"|\"B\" */\ntype StringToUnion2\u003cS\u003e = S extends `${infer F}${infer R}`\n\t? F | StringToUnion2\u003cR\u003e\n\t: S\n```\n\n### 判断是否元组\n\n- 数组和元组的区别之一就是数组的长度是不固定的,类型为 number,而元组长度是固定的,类型为具体的数字\n\n```ts\ntype IsTuple\u003cT\u003e = /** 判断never */ [T] extends [never]\n\t? false\n\t: /** 知识点,判断 readonly,是为了屏蔽 lengtlike 对象{length:3}(数组和元组的长度是只读的) */ T extends readonly unknown[]\n\t? /** 知识点,数组和元组的区别之一就是数组的长度是不固定的,类型为 number,而元组长度是固定的,类型为具体的数字 */ number extends T['length']\n\t\t? false\n\t\t: true\n\t\t: false\n\t\t\n/** number extends T['length'] 怎么生效的? */\n/** T['length']是 number 时,number extends number -\u003e true */\n/** T['length']是 具体的1,4,9 时,number extends 1 -\u003e false,number 的范围比1大 */\n\n/** number extends T['length'] 可以用 T['length'] extends number 替代吗? */\n/** T['length']无论为 number 或 1,4,9时,其 extends number 都为 true */\n/** number extends number -\u003e true */\n/** 1 extends number -\u003e true */\n/** 所以不能调换 */\n```\n\n### infer 时可以直接约束推断的值为某类型\n\n```ts\ntype Join\u003cT extends any[], U extends number | string\u003e = T extends [\n\t/** 知识点:在infer 时用 extends 直接将 First 推断为 string */\n\tinfer F extends string,\n\t...infer R\n]\n\t? R['length'] extends 0\n\t\t? `${F}`\n\t\t: `${F}${U}${Join\u003cR, U\u003e}`\n\t: ''\n```\n\n### 如何遍历数组\n\n```ts\n// 递归+infer 取值\ntype A\u003cT\u003e = T extends [infer F,...infer R]?A\u003cR\u003e:never\n```\n\n### 如何从后向前遍历数组\n\n```ts\ntype LastIndexOf\u003cT extends unknown[], U\u003e = T extends [\n\t/** 知识点:从后往前遍历数组 */\n\t...infer Rest,\n\tinfer Last\n]\n? MyEqual\u003cLast, U\u003e extends true\n\t? /** 知识点:用前面数组的 length 代表当前元素索引 */\n\tRest['length']\n\t: LastIndexOf\u003cRest, U\u003e\n: -1\n```\n\n### 如何将数字取整\n\n```ts\n/** 思路:先转成字符串,再和 bigint 比较 */\n/** 知识点,bingint 只能为整数https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/BigInt */\ntype Integer\u003cT extends number\u003e = `${T}` extends `${bigint}` ? T : never\n```\n\n### 如何获取返回类型值的原始类型\n\n```ts\n/** 知识点,ts中的 valueOf 可以返回类型值的原始类型 */\ntype a = 3\ntype b\u003cT\u003e = T extends { valueOf: () =\u003e infer R } ? R : T\ntype c = b\u003ca\u003e // number\n```\n\n## 内置工具类型\n\n### Awaited\u003cType\u003e - 获取异步操作的返回类型\n\n```ts\ntype A = Awaited\u003cPromise\u003cstring\u003e\u003e; // string\ntype B = Awaited\u003cPromise\u003cPromise\u003cnumber\u003e\u003e\u003e; //number\ntype C = Awaited\u003cboolean | Promise\u003cnumber\u003e\u003e; // boolean | number\n```\n\n### Partial\u003cType\u003e - 将类型中的所有属性转换为可选属性\n```ts\ninterface Todo {\n  title: string;\n  description: string;\n}\n\ntype A = Partial\u003cTodo\u003e\n\n/**\ntype A = {\n    title?: string | undefined;\n    description?: string | undefined;\n}\n */\n```\n\n### Required\u003cType\u003e - 将类型中的所有可选属性转换为必选属性\n```ts\ninterface Props {\n  a?: number;\n  b?: string;\n}\n\ntype A = Required\u003cProps\u003e\n\n/**\ntype A = {\n    a: number;\n    b: string;\n}\n*/\n```\n\n### Readonly\u003cType\u003e - 将类型中的所有属性转换为只读属性\n```ts\ninterface Todo {\n  title: string;\n}\n\ntype A = Readonly\u003cTodo\u003e\n\n/**\n type A = {\n    readonly title: string;\n}\n*/\n```\n\n### Record\u003cKeys, Type\u003e - 创建一个具有指定键和值类型的对象\n```ts\ninterface CatInfo {\n  age: number;\n  breed: string;\n}\n \ntype CatName = \"miffy\" | \"boris\" | \"mordred\";\n\ntype A  = Record\u003cCatName, CatInfo\u003e \n\n/**\ntype A = {\n    miffy: CatInfo;\n    boris: CatInfo;\n    mordred: CatInfo;\n}\n*/\n```\n\n### Pick\u003cType, Keys\u003e - 从类型中挑选指定的属性\n```ts\ninterface Todo {\n  title: string;\n  description: string;\n  completed: boolean;\n}\n \ntype TodoPreview = Pick\u003cTodo, \"title\" | \"completed\"\u003e;\n\n/** \ntype TodoPreview = {\n    title: string;\n    completed: boolean;\n}\n*/\n```\n\n### Omit\u003cType, Keys\u003e - 从类型中排除指定的属性\n```ts\ninterface Todo {\n  title: string;\n  description: string;\n  completed: boolean;\n  createdAt: number;\n}\n \ntype TodoPreview = Omit\u003cTodo, \"description\"\u003e;\n\n/**\ntype TodoPreview = {\n    title: string;\n    completed: boolean;\n    createdAt: number;\n}\n*/\n```\n\n### Exclude\u003cUnionType, ExcludedMembers\u003e - 从联合类型中排除指定的成员\n```ts\ntype T0 = Exclude\u003c\"a\" | \"b\" | \"c\", \"a\"\u003e; // \"b\" | \"c\"\ntype T1 = Exclude\u003c\"a\" | \"b\" | \"c\", \"a\" | \"b\"\u003e; // \"c\"\n```\n\n### Extract\u003cType, Union\u003e - 从联合类型中提取指定的成员\n```ts\ntype T0 = Extract\u003c\"a\" | \"b\" | \"c\", \"a\"\u003e; // \"a\"\n```\n\n### NonNullable\u003cType\u003e - 从类型中排除 `null` 和 `undefined`\n```ts\ntype T0 = NonNullable\u003cstring | number | undefined\u003e; // string | number\ntype T1 = NonNullable\u003cstring[] | null | undefined\u003e; // string[]\n```\n\n### Parameters\u003cType\u003e - 获取函数类型的参数类型\n```ts\ndeclare function f1(arg: { a: number; b: string }): void;\n\ntype T0 = Parameters\u003c() =\u003e string\u003e; // []\ntype T1 = Parameters\u003c(s: string) =\u003e void\u003e; // [s: string]\ntype T2 = Parameters\u003c\u003cT\u003e(arg: T) =\u003e T\u003e; // [arg: unknown]\ntype T3 = Parameters\u003ctypeof f1\u003e;\n/**\ntype T3 = [arg: {\n    a: number;\n    b: string;\n}]\n*/\ntype T4 = Parameters\u003cany\u003e; // unknown[]\ntype T5 = Parameters\u003cnever\u003e;// never\ntype T6 = Parameters\u003cstring\u003e; // error\ntype T7 = Parameters\u003cFunction\u003e; // error\n```\n\n### ConstructorParameters\u003cType\u003e - 获取构造函数类型的参数类型\n```ts\ntype T0 = ConstructorParameters\u003cErrorConstructor\u003e; // [message?: string | undefined]\ntype T1 = ConstructorParameters\u003cFunctionConstructor\u003e; // string[]\ntype T2 = ConstructorParameters\u003cRegExpConstructor\u003e; // [pattern: string | RegExp, flags?: string | undefined]\nclass C {\n  constructor(a: number, b: string) {}\n}\ntype T3 = ConstructorParameters\u003ctypeof C\u003e; // [a: number, b: string]\ntype T4 = ConstructorParameters\u003cany\u003e; // unknown[]\ntype T5 = ConstructorParameters\u003cFunction\u003e; // error\n```\n\n### ReturnType\u003cType\u003e - 获取函数类型的返回值类型\n```ts\ndeclare function f1(): { a: number; b: string };\n\ntype T0 = ReturnType\u003c() =\u003e string\u003e; // string\ntype T1 = ReturnType\u003c(s: string) =\u003e void\u003e; // void\ntype T2 = ReturnType\u003c\u003cT\u003e() =\u003e T\u003e; // unknown\ntype T3 = ReturnType\u003c\u003cT extends U, U extends number[]\u003e() =\u003e T\u003e; // number[]\ntype T4 = ReturnType\u003ctypeof f1\u003e; // { a: number; b: string;}\ntype T5 = ReturnType\u003cany\u003e; // any\ntype T6 = ReturnType\u003cnever\u003e; // never\ntype T7 = ReturnType\u003cstring\u003e; // error any\ntype T8 = ReturnType\u003cFunction\u003e; // error any\n```\n\n### InstanceType\u003cType\u003e - 获取构造函数类型的实例类型\n```ts\nclass C {\n  x = 0;\n  y = 0;\n}\n\ntype T0 = InstanceType\u003ctypeof C\u003e; // C \ntype T1 = InstanceType\u003cany\u003e; // any\ntype T2 = InstanceType\u003cnever\u003e; // never\ntype T3 = InstanceType\u003cstring\u003e; // error any\ntype T4 = InstanceType\u003cFunction\u003e; // error any\n```\n\n### ThisParameterType\u003cType\u003e - 获取函数类型中的 `this` 参数类型\n```ts\nfunction toHex(this: Number) {\n  return this.toString(16);\n}\n\nfunction numberToString(n: ThisParameterType\u003ctypeof toHex\u003e /** Number */) {\n  return toHex.apply(n);\n}\n```\n\n### OmitThisParameter\u003cType\u003e - 从函数类型中移除 `this` 参数\n```ts\nfunction toHex(this: Number) {\n  return this.toString(16);\n}\n\nconst fiveToHex:/** () =\u003e string */ OmitThisParameter\u003ctypeof toHex\u003e = toHex.bind(5);\n```\n\n### ThisType\u003cType\u003e - 用于指定函数中的 `this` 类型\n```ts\n/**\n* 这段 TypeScript 代码定义了一个 makeObject 函数\n* 该函数接受一个 ObjectDescriptor 类型的参数 desc，该类型包含两个属性：data 和 methods。\n* 其中，data 属性是一个泛型 D 类型的对象，而 methods 属性是一个泛型 M 类型的对象，同时约束了 methods 中的方法的 this 上下文类型为 D \u0026 M。\n* 在 makeObject 函数中，首先根据传入的 desc 参数初始化了 data 和 methods 对象。\n* 然后，通过展开运算符 { ...data, ...methods } 将这两个对象合并为一个新对象，并通过类型断言 as D \u0026 M 将其断言为类型 D \u0026 M，最后返回该新对象。\n* 通过调用 makeObject 函数，并传入包含 data 和 methods 的对象作为参数，我们可以创建一个具有指定数据和方法的对象。\n* 其中，特别值得注意的是，methods 中的方法可以在方法体中使用强类型的 this 上下文\n* 这是因为 methods 的类型声明中使用了 ThisType\u003cD \u0026 M\u003e，指明了方法中 this 的类型。\n* 这样，方法可以访问对象的数据属性，同时也能访问其他方法。\n */\ntype ObjectDescriptor\u003cD, M\u003e = {\n  data?: D;\n  methods?: M \u0026 ThisType\u003cD \u0026 M\u003e; // 指定methods中的this为D \u0026 M\n};\n\nfunction makeObject\u003cD, M\u003e(desc: ObjectDescriptor\u003cD, M\u003e): D \u0026 M {\n  let data: object = desc.data || {};\n  let methods: object = desc.methods || {};\n  return { ...data, ...methods } as D \u0026 M;\n}\n\nlet obj = makeObject({\n  data: { x: 0, y: 0 },\n  methods: {\n    moveBy(dx: number, dy: number) {\n      this.x += dx; // Strongly typed this\n      this.y += dy; // Strongly typed this\n    },\n  },\n});\n\nobj.x = 10;\nobj.y = 20;\nobj.moveBy(5, 5);\n```\n\n### Uppercase\u003cStringType\u003e - 将字符串转换为大写\n```ts\ntype A = Uppercase\u003c'hello'\u003e // 'HELLO'\n```\n\n### Lowercase\u003cStringType\u003e - 将字符串转换为小写\n```ts\ntype B =  Lowercase\u003c'HELLO'\u003e // hello\n```\n\n### Capitalize\u003cStringType\u003e - 将字符串首字母转换为大写\n```ts\ntype C =  Capitalize\u003c'hello'\u003e // Hello\n```\n\n### Uncapitalize\u003cStringType\u003e - 将字符串首字母转换为小写\n```ts\ntype D =  Uncapitalize\u003c'Hello'\u003e // hello\ntype E =  Uncapitalize\u003c'HELLO'\u003e // hELLO\n```\n\n## 参考\n\n- https://github.com/type-challenges/type-challenges\n- https://www.bilibili.com/video/BV1vY41187Tx\n- https://wangtunan.github.io/blog/typescript/challenge.html\n- https://blog.thoughtspile.tech/2023/01/23/typescript-sets/\n- https://ivov.dev/notes/typescript-and-set-theory\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbryanadamss%2Ftype-challenges-analysis","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbryanadamss%2Ftype-challenges-analysis","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbryanadamss%2Ftype-challenges-analysis/lists"}