【第二届青训营-寒假前端场】- 「TypeScript入门」笔记
2012-10:微软发布了TypeScript第一个版本(0.8)
2014-10:Angular 发布了基于TypeScript的2.0版本
2015-04:微软发布了Visual Studio Code
2016-05:@ ty pes/react发布,TypeScript 可开发React
2020-09:Vue 发布了3.0 版本,官方支持TypeScript
2021-11:v4.5版本发布
动态类型在执行过程中进行类型的匹配,js的弱类型会在执行时进行隐式类型转换,而在静态类型中则不然
TypeScript则为静态类型:java、c/c++等
可读性增强:基于语法解析TSDoc,ide增强
可维护性增强:在编译阶段暴露大部分错误
多人合作的大型项目中,可以获得更好的稳定性和开发效率
TypeScript是JS的超集
包含于兼容所有Js特性, 支持共存
支持渐进式引入与升级
js ==> ts
可以看到,ts的类型定义方式:let 变量名: 类型 = 值;
js:
ts:
可以看到,格式为function 函数名(参数:类型...):返回值类型
简化形式如下:
type作用就是给类型起一个新名字,相当于c++中的typedef
空类型:表示无赋值
任意类型:是所有类型的子类型
枚举类型:支持枚举值到枚举名的正、反向映射
泛型,之前学过c++的话dddd,跟c++中的差不多:不预先指定具体的类型,而在使用的时候再指定类型的一种特性
泛型还可以使用在以下场景中:
泛型还可以进行约束范围
类型断言有时候你会遇到这样的情况,你会比TypeScript更了解某个值的详细信息。 通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。
通过类型断言 这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript会假设你,程序员,已经进行了必须的检查。
上述代码,中有几个点需注意:
函数对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。
语法:
arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
为书籍列表编写类型 -> ts类型声明繁琐存在较多重复。
联合类型: IA | IB; 联合类型表示一个值可以是几种类型之一
交叉类型: IA & IB; 多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
上述代码可以通过ts简化为:
访问联合类型时,处于程序安全,仅能访问联合类型中的交集部分
上述报错可通过类型守卫解决:定义一个函数,其返回值是一个类型谓词,生效范围为子作用域
或者typeof和instance判断
不会每次都这么麻烦吧,事实上,只有当两个类型没有任何重合点的话才需要类型守卫,如上述的书本例子,可以进行自动类型推断。
再来看一个case,实现一个子集不污染的合并函数merge,将sourceObj合并到targetObj中,sourceObj必须为targetObj的子集
而一种简单的思想就是在ts中编写两个类型,进行判断,但这样又会存在实现繁琐,增加target需要source联动去除,重复维护了两份x、y
通过泛型,改进,这里涉及到几个个知识点
Partial:一个常见的任务是将一个已知的类型每个属性都变为可选的
TypeScript提供了从旧类型中创建新类型的一种方式——映射类型。 在映射类型里,新类型以相同的形式去转换旧类型里每个属性。 (直接写就行,ts内置了)
关键字keyof,其相当于取值对象中的所有key组成的字符串字面量
关键字in,其相当于取值字符串字面量中的一种可能,
函数返回值类型在定义时候是不明确的,也应该通过泛型进行表达
下文代码delayCall接受一个函数作为入参,其实现延迟1s运行函数func,其返回promise,结果为入参函数的返回结果
关键字 extends 跟随泛型出现时,表示类型推断,其表达可类比三元表达式
如T === 判断类型?类型A:类型B -> T extends 判断类型?类型A:类型B
关键字 infer 出现在类型推荐中,表示定义类型变量
配置webapack loader相关配置
配置tsconfig.js文件(宽松——严格,都可以定义)
运行webpack启动/ 打包
loader处理ts文件时, 会进行编译与类型检查
相关loader:
or
使用TSC编译
安装Node与npm
配置tsconfig.js文件
使用npm安装tsc
使用tsc运行编译得到js文件
这节课老师讲了TypeScript的用处与基本语法、和JS的对比、高级类型的应用,后续也深入讲了一下类型保护与类型守卫,在最后总结了TypeScript如何在工程中进行应用。TypeScript作为JS的一个超集,他增加了类型检查的功能,可以在编译阶段就将代码中的错误暴露出来,这是js这类动态类型所不具备的,在多人合作的大型项目中,使用TS往往可以获得更好的稳定性和开发效率。
本文引用的大部分内容来自林皇老师的课以及ts官方文档~
关键字 ? ,通过设定对象可选选项,即可自动推导出子集类型
infer 简单示例如下:
在这个条件语句
T extends (...args: infer P) => any ? P : T中,infer P表示待推断的函数参数。整句表示为:如果
T能赋值给(...args: infer P) => any,则结果是(...args: infer P) => any类型中的参数P,否则返回为T。
在这里就相当于把这个函数返回值类型指代为R
type ParamType<T> = T extends (...args: infer P) => any ? P : T;// 创建一个对象,包括以下属性,类型为IBytedancer
// I表示自定义的一个类型(一个命名约定),与类和对象进行区分
const bytedancer: IBytedancer = {
jobId: 9303245,
name: 'Lin',
sex: 'man',
age: 28,
hobby: 'swimming',
}
// 定义一个类型为IBytedancer
interface IBytedancer {
/* 只读属性readonly:约束属性不可在对象初始化外赋值 */
readonly jobId: number;
name: string;
sex: 'man' | 'woman' | 'other';
age: number;
/* 可选属性:定义该属性可以不存在 */
hobby?: string;
/* 任意属性:约束所有对象属性都必须是该属性的子类型 */
[key: string]: any; // any 任何类型
}
/* 报错:无法分配到"jobId",因为它是只读属性 */
bytedancer. jobId = 12345;
/* 成功:任意属性标注下可以添加任意属性 */
bytedancer .plateform = 'data';
/* 报错:缺少属性"name", 而hobby可缺省 */
const bytedancer2: IBytedancer = {
jobId: 89757,
sex: "woman",
age: 18,
}function add(x, y!) {
return x + y;
}
const mult = (x, y) => x * y;function add(x: number, y: number): number {
return x + y;
}
const mult: (x: number, y: number) => number = (x, y) => x * y;
// 简化写法,定义接口IMult
interface IMult {
(x: number, y: number): number ;
}
const mult: IMult = (x, y) => x * y;/* 对getDate函数进行重载,timestamp为可缺省参数 */
function getDate(type: 'string', timestamp?: string): string;
function getDate(type: 'date', timestamp?: string): Date;
function getDate(type: 'string' | 'date', timestamp?: string): Date | string {
const date = new Date(timestamp);
return type === 'string' ? date.toLocaleString() : date;
};
const x = getDate('date'); // x: Date
const y = getDate('string', '2018-01-10'); // y: stringinterface IGetDate {
(type : 'string', timestamp ?: string): string; // 这个地方返回类型改为any就可以通过了
(type : 'date', timestamp?: string): Date;
(type: 'string' | 'date', timestamp?: string): Date | string;
}
/* 报错:不能将类型"(type: any, timestamp: any) => string | Date"分配给类型"IGetDate"。
不能将类型"string | Date" 分配给类型"string"。
不能将类型 "Date"分配给类型"string"。ts(2322) */
const getDate2: IGetDate = (type, timestamp) => {
const date = new Date( timestamp) ;
return type === 'string' ? date.toLocaleString() : date;
}/* 「类型+方括号」表示 */
type IArr1 = number[];
/* 泛型表示 这两种最常用*/
type IArr2 = Array<string | number| Record<string, number> > ;
/* 元组表示 */
type IArr3 = [number, number, string, string];
/* 接口表示 */
interface IArr4 {
[key: number]: any;
}
const arrl: IArr1 = [1, 2, 3, 4, 5, 6];
const arr2: IArr2 = [1, 2, '3', '4', { a: 1 }];
const arr3: IArr3 = [1, 2, '3', '4'];
const arr4: IArr4 = ['string', () => null, {}, []];/* 空类型,表示无赋值 */
type IEmptyFunction = () => void;
/* 任意类型,是所有类型的子类型 */
type IAnyType = any;
/* 枚举类型:支持枚举值到枚举名的正、反向映射 */
enum EnumExample {
add = '+',
mult = '*',
}
EnumExample['add'] === '+';
EnumExample['+'] === 'add';
enum ECorlor { Mon, Tue, Wed, Thu, Fri, Sat, Sun };
ECorlor['Mon'] === 0;
ECorlor[0] === 'Mon' ;
/*泛型*/
type INumArr = Array<number>;function getRepeatArr(target) {
return new Array(100).fill(target);
}
type IGetRepeatArr = (target: any) => any[];
/* 不预先指定具体的类型,而在使用的时候再指定类型的一种特性 */
type IGetRepeatArrR = <T>(target: T) => T[];/*泛型接口&多泛型*/
interface IX<T, U> {
key: T;
val: U;
}
/* 泛型类 */
class IMan<T> {
instance: T;
}
/* 泛型别名 */
type ITypeArr<T> = Array<T>;/* 泛型约束:限制泛型必须符合字符串 */
type IGetRepeatStringArr = <T extends string>(target: T) => T[];
const getStrArr: IGetRepeatStringArr = target => new Array(100).fill(target);
/* 报错:类型"number"的参数不能赋给类型“string"的参数 */
getStrArr(123) ;
/* 泛型参数默认类型 */
type IGetRepeatArr<T = number> = (target: T) => T[];// 与结构中的默认赋值有点类似
const getRepeatArr: IGetRepeatArr = target => new Array(100).fill(target);// 这里的IGetRepeatArr就是一个类型别名,此处没有传参数给这个类型别名
/* 报错:类型"string"的参数不能赋给类型“numbe r"的参数 */
getRepeatArr('123');let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;/*通过type关键字定义了IObjArr的别名类型*/
type IObjArr = Array<{
key: string;
[objKey: string]: any;
}>
function keyBy<T extends IObjArr>(objArr: Array<T>) {
/* 未指定类型时,result类型为{} */
const result = objArr.reduce((res, val, key) => {
res[key] = val;
return res;
}, {});
/* 通过as关键字,断言result类型为正确类型 */
return result as Record<string, T> ;
}/* 允许指定字符 串/数字必须的固定值*/
/* IDomTag必须为html、body、div、 span中的其一*/
type IDomTag = 'html' | ' body' | 'div' | 'span';
/* IOddNumber必须为1、 3、5、7、9中的其一 */
type IOddNumber = 1 | 3 | 5 | 7 | 9;const bookList = [ { // 普通js
author:'xiaoming',
type:'history',
range: '2001 -2021',
}, {
author:'xiaoli',
type:'Story',
theme:'love',
}]
// ts 繁琐
interface IHistoryBook {
author:String;
type:String;
range:String
}
interface IStoryBook {
author:String;
type:String;
theme:String;
}
type IBookList = Array<IHistoryBook | IStoryBook>;type IBookList = Array<{
author: string;
} & ({
type: 'history';
range: string;
} | {
type: 'story';
theme: string;
})>;
/* 限制了author只能为string类型,而type只能'history'/'story'二选一,并且type不同可能的属性不同 */interface IA { a: 1, a1: 2 }
interface IB { b: 1, b1: 2 }
function log(arg: IA | IB) {
/*报错:类型"IA | IB" 上不存在属性"a”。 类型"IB"上不存在属性"a"
结论:访问联合类型时,处于程序安全,仅能访问联合类型中的交集部分*/
if(arg.a) {
console.log(arg.a1);
} else {
console.log(arg.b1);
}
}interface IA { a: 1, a1: 2 }
interface IB { b: 1, b1: 2 }
/*类型守卫:定义一个函数,。它的返回值是一个类型谓词,生效范围为子作用域 */
function getIsIA(arg: IA | IB): arg is IA {
return !!(arg as IA).a;
}
function log2(arg: IA | IB) {
/* 不存在报错了 */
if(getIsIA(arg) ) {
console.log(arg.a1);
} else {
console.log(arg.b1);
}
}// 实现函数reverse 可将数组或字符串进行反转
function reverse(target: string | Array<any>) {
/* typof 类型保护*/
if (typeof target === 'string') {
return target.split('').reverse().join('');
}
/* instance 类型保护*/
if (target instanceof Object) {
return target.reverse() ;
}
}// 实现函数logBook类型
// 函数接受书本类型,并logger出相关特征
function logBook(book: IBookItem) {
// 联合类型+类型保护=自动类型推断
if (book.type === 'history'){
console.log(book.range)
} else{
console.log book.theme);
}
}function merge1(sourceObj, targetObj) { // js中,实现复杂,这样才能不污染
const result = { ...sourceObj };
for(let key in targetObj) {
const itemVal = sourceObj[key];
itemVal && ( result[key] = itemVal );
}
return result;
}
function merge2(sourceObj, targetObj) {// 若这两个入参的类型没问题,则可以这样
return { ...sourceObj, ...targetObj };
}interface ISource0bj {
x?: string;
y?: string;
}
interface ITarget0bf {
x: string;
y: string;
}
type IMerge = (source0bj: ISource0bj, target0bj: ITarget0bj) => ITargetObj;
/* 类型实现繁琐:若obj类型较为复杂,则声明source和target便需要大量重复2遍
容易出错:若target增加/减少key,则需要source联动去除 */interface IMerge {
<T extends Record<string, any>>(sourceObj: Partial<T>, targetObj: T): T;
}
// Partial内部实现
type IPartial<T extends Record<string, any>> = {
[P in keyof T]?:T[P];
}
// 索引类型:关键字[keyof] ,其相当于取值对象中的所有key组成的字符串字面量,如
type IKeys = keyof{a: string; b: number }; // => type IKeys ="a" | "b"
// 关键字[in],其相当于取值 字符串字面量中的一种可能,配合泛型P, 即表示每个key
// 关键字[ ? ],通过设定对象 可选选项,即可自动推导出子集类型// 如何实现函数delayCall的类型声明
// delayCall接受一个函数作为入参,其实现延迟1s运行函数
// 其返回promise,结果为入参函数的返回结果
function delayCall(func) {
return new Promisd(resolve => {
setTimeout(() => {
const result= func );
resolve(result);
},1000);
});
}type IDelayCall= <T extends () => any>(func: T) => ReturnType<T>;
type IReturnType<T extends (...args: any) => any> = T extends(...args: any ) => inferR ? R : any
// 关键字[extends] 跟随泛型出现时,表示类型推断,其表达可类比三元表达式
// 如T === 判断类型?类型A:类型B
// 关键字[infer] 出现在类型推荐中,表示定义类型变量,可以用于指代类型
// 如该场景下,将函数的返回值类型作为变量,使用新泛型R表示,使用在类型推荐命中的结果中想玩玩儿类型体操不?很有意思的哦
前不久一位朋友,在面试中用体操写出 Flat,把我给震惊到了,了解后就觉得 ts 的类型体操相当有趣
题目来自于大名鼎鼎的体操仓库:type-challenges
学习来自于大名鼎鼎的掘金小册:TypeScript 类型体操通关秘籍 不得不说是一本很不错的小册
Easy 题共13道,是真的都很easy
通过 keyof 取出 T 类型的所有键,如果只是 MyPick<T, K>的话,会报错如下
https://tsch.js.org/11/play/zh-CN
符合小册中模式匹配做提取套路,通过 extends 对类型参数做匹配,结果保存到通过 infer 声明的局部类型变量里,若匹配就能从该局部变量里拿到提取出的类型。
会抛出 Error 说明得限制传入的类型
这题可能看的人一头雾水,但是可以看看这里:
也就是分别判断 T 中的每个联合类型,是否 extends U
这里用到了 这个类型
这题也比较基础,考的就是 extends
在类型系统里实现 JavaScript 内置的 Array.concat 方法,这个类型接受两个参数,返回的新数组类型应该按照输入参数从左到右的顺序合并为一个新的数组。
在类型系统里实现 JavaScript 的 Array.includes 方法,这个类型接受两个参数(左边参数是否 include 右边参数),返回的类型要么是 true 要么是 false。
运用了小册中的套路三:递归复用做循环,
type MyPick<T, K extends keyof T> = {
[Key in K]: T[Key]
}Type 'K' is not assignable to type 'string | number | symbol'.
Type 'key' cannot be used to index type 'T'.type MyReadonly<T> = {
readonly [Key in keyof T]: T[Key]
}type TupleToObject<T extends readonly any[]> = {
[Key in T[number]]: Key
}type First<T extends any[]> = T extends [infer F, ...any[]] ? F : nevertype Length<T extends readonly unknown[]> = T['length']type MyExclude<T, U> = T extends U ? never : Ttype ToArray<Type> = Type extends any ? Type[] : never
// 如果我们将联合类型插入 ToArray,则条件类型将应用于该联合的每个成员。
type StrArrOrNumArr = ToArray<string | number>
// ^type StrArrOrNumArr = string[] | number[]type MyAwaited<T extends PromiseLike<any>> = T extends PromiseLike<infer V>
? V extends PromiseLike<any>
? MyAwaited<V>
: V
: nevertype If<C extends boolean, T, F> = C extends true ? T : Ftype Concat<T extends unknown[], U extends unknown[]> = [...T, ...U]type IsEqual<A, B> = (A extends B ? true : false) & (B extends A ? true : false)
type Includes<T extends readonly any[], U> = T extends [infer F, ...infer Rest]
? IsEqual<F, U> extends true
? true
: Includes<Rest, U>
: falsetype Push<T, U> = T extends [...infer Rest] ? [...Rest, U] : nevertype Unshift<T, U> = T extends [...infer Rest] ? [U, ...Rest] : nevertype MyParameters<T extends (...args: any[]) => any> = T extends (...args: infer A) => any ? A : neverMiddle题共72道,是类型体操的大头
不使用 ReturnType 实现 TypeScript 的 ReturnType<T> 泛型。
例如:
正确解答:
注意参数数组~
不使用 Omit 实现 TypeScript 的 Omit<T, K> 泛型。
Omit 会创建一个省略 K 中字段的 T 对象。
例如:
正确解答:
乍一看很绕,其实搞懂 keyof 和 in 就明白了。
实现一个通用 MyReadonly2<T, K>,它带有两种类型的参数 T和 K。
K指定应设置为 Readonly 的 T的属性集。如果未提供 K,则应使所有属性都变为只读,就像普通的 Readonly<T>一样。
例如
正确解答
结合上面的 和简单题中的 可得出该题的答案。
实现一个通用的 DeepReadonly<T>,它将对象的每个参数及其子对象递归地设为只读。 您可以假设在此挑战中我们仅处理对象。数组,函数,类等都无需考虑。但是,您仍然可以通过覆盖尽可能多的不同案例来挑战自己。
例如
正确解答
递归解决,注意特判 Funtion 的情况
实现泛型 TupleToUnion<T>,它返回元组所有值的合集。
例如
解答: 没想太多的递归实现 👇
翻了翻 issue 发现的另一个实现,确实哦!
我的解答
在 JavaScript 中我们经常会使用可串联(Chainable/Pipeline)的函数构造一个对象,但在 TypeScript 中,你能合理的给它赋上类型吗?
在这个挑战中,你可以使用任意你喜欢的方式实现这个类型 - Interface, Type 或 Class 都行。你需要提供两个函数 option(key, value) 和 get()。在 option 中你需要使用提供的 key 和 value 扩展当前的对象类型,通过 get 获取最终结果。
例如
你只需要在类型层面实现这个功能 - 不需要实现任何 TS/JS 的实际逻辑。
你可以假设 key 只接受字符串而 value 接受任何类型,你只需要暴露它传递的类型而不需要进行任何处理。同样的 key 只会被使用一次。
正确解答:注意 Key 必须为之前没取过的,或是 Value 类型与之前不同的(用例 2 和 3)。
实现一个通用 Last<T>,它接受一个数组 T并返回其最后一个元素的类型。
正确解答:基本送分题
实现一个通用 Pop<T>,它接受一个数组 T,并返回一个由数组 T的前 length-1 项以相同的顺序组成的数组。
例如
额外:同样,您也可以实现 Shift,Push和 Unshift吗?
正确解答:看了小册后不难做出
键入函数 PromiseAll,它接受 PromiseLike 对象数组,返回值应为 Promise<T>,其中 T是解析的结果数组。
正确解答:
疑惑吗?疑惑就对了,请看看这个 pr :
有时,您可能希望根据某个属性在联合类型中查找类型。
在此挑战中,我们想通过在联合类型 Cat | Dog中搜索公共 type字段来获取相应的类型。换句话说,在以下示例中,我们期望 LookUp<Dog | Cat, 'dog'>获得 Dog,LookUp<Dog | Cat, 'cat'>获得 Cat。
正确解答:第一反应如下
翻了翻 issue,很巧妙地如下:
实现 TrimLeft<T> ,它接收确定的字符串类型并返回一个新的字符串,其中新返回的字符串删除了原字符串开头的空白字符串。
例如
正确解答:非常经典的!
实现Trim<T>,它是一个字符串类型,并返回一个新字符串,其中两端的空白符都已被删除。
例如
正确解答:结合上一题,易得 TrimRight 的实现方法,二者结合即可实现 Trim
也可以用:
实现 Capitalize<T> 它将字符串的第一个字母转换为大写,其余字母保持原样。
例如
正确解答:还是通过模式匹配,使用内置类型 Uppercase 进行大写转换
实现 Replace<S, From, To> 将字符串 S 中的第一个子字符串 From 替换为 To 。
例如
正确解答:注意 From 为空的情况
实现 ReplaceAll<S, From, To> 将一个字符串 S 中的所有子字符串 From 替换为 To。
例如
正确解答:在上题基础上,递归!
实现一个泛型 AppendArgument<Fn, A>,对于给定的函数类型 Fn,以及一个任意类型 A,返回一个新的函数 G。G 拥有 Fn 的所有参数并在末尾追加类型为 A 的参数。
正确解答:显而易见,本题 easy
实现联合类型的全排列,将联合类型转换成所有可能的全排列数组的联合类型。
正确解答:题目字越少,难度越大(x)
本题实现全排列,同时要利用联合类型的分散传参这个特性
翻 issue 的时候翻到了这个优秀的解答:
[T] extends [never] 是由于 never 无法被准确判断,任何extends never的条件语句都会返回never,而我们需要返回的是空数组,在小册中的 有提到这一点。
计算字符串的长度,类似于 String#length 。
正确解答:不讲武德版当然就是 type LengthOfString<S extends string> = S['length'] 当然,讲武德版就是这样 ⬇️
在这个挑战中,你需要写一个接受数组的类型,并且返回扁平化的数组类型。
例如:
正确解答:经典的数组拍平,需要注意的是...展开需在外层,Item extends any[] ? ...Flatten<Item> : Item 是不行的......
实现一个为接口添加一个新字段的类型。该类型接收三个参数,返回带有新字段的接口类型。
例如:
正确解答:先将交叉类型整出来,再把交叉类型遍历一遍。
看到一种很有趣的
const fn = (v: boolean) => {
if (v) return 1
else return 2
}
type a = MyReturnType<typeof fn> // 应推导出 "1 | 2"type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : neverinterface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyOmit<Todo, 'description' | 'title'>
const todo: TodoPreview = {
completed: false,
}type MyOmit<T, K extends keyof T> = {
[Key in keyof T as Key extends K ? never : Key]: T[Key]
}interface Todo {
title: string
description: string
completed: boolean
}
const todo: MyReadonly2<Todo, 'title' | 'description'> = {
title: 'Hey',
description: 'foobar',
completed: false,
}
todo.title = 'Hello' // Error: cannot reassign a readonly property
todo.description = 'barFoo' // Error: cannot reassign a readonly property
todo.completed = true // OKtype MyReadonly2<T, K extends keyof T = keyof T> = {
readonly [Key in K]: T[Key]
} & Omit<T, K>type X = {
x: {
a: 1
b: 'hi'
}
y: 'hey'
}
type Expected = {
readonly x: {
readonly a: 1
readonly b: 'hi'
}
readonly y: 'hey'
}
type Todo = DeepReadonly<X> // should be same as `Expected`type DeepReadonly<T extends Object> = {
readonly [Key in keyof T]: T[Key] extends Record<any, any>
? T[Key] extends Function
? T[Key]
: DeepReadonly<T[Key]>
: T[Key]
}type Arr = ['1', '2', '3']
type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3'type TupleToUnion<T> = T extends [infer V, ...infer Rest] ? V | TupleToUnion<Rest> : nevertype TupleToUnion<T extends any[]> = T[number]type Chainable = {
option(key: string, value: any): any
get(): any
}
declare const config: Chainable
const result = config.option('foo', 123).option('name', 'type-challenges').option('bar', { value: 'Hello World' }).get()
// 期望 result 的类型是:
interface Result {
foo: number
name: string
bar: {
value: string
}
}type Chainable<Obj extends Record<string, any> = {}> = {
option<K extends string, V>(
key: K extends keyof Obj ? (V extends Obj[K] ? never : V) : K,
value: V,
): Chainable<Omit<Obj, K> & Record<K, V>>
get(): Obj
}type Last<T extends any[]> = T extends [...infer Rest, infer L] ? L : nevertype arr1 = ['a', 'b', 'c', 'd']
type arr2 = [3, 2, 1]
type re1 = Pop<arr1> // expected to be ['a', 'b', 'c']
type re2 = Pop<arr2> // expected to be [3, 2]type Pop<T extends any[]> = T extends [...infer Rest, infer L] ? Rest : T
type Push<T extends any[], Arr extends any[]> = [...T, ...Arr]
type Shift<T extends any[]> = T extends [infer F, ...infer Rest] ? Rest : T
type Unshift<T extends any[], Arr extends any[]> = [...Arr, ...T]const promise1 = Promise.resolve(3)
const promise2 = 42
const promise3 = new Promise<string>((resolve, reject) => {
setTimeout(resolve, 100, 'foo')
})
// expected to be `Promise<[number, 42, string]>`
const p = PromiseAll([promise1, promise2, promise3] as const)declare function PromiseAll<Args extends readonly unknown[]>(
values: readonly [...Args],
): Promise<{ [Key in keyof Args]: Args[Key] extends Promise<infer V> ? V : Args[Key] }>interface Cat {
type: 'cat'
breeds: 'Abyssinian' | 'Shorthair' | 'Curl' | 'Bengal'
}
interface Dog {
type: 'dog'
breeds: 'Hound' | 'Brittany' | 'Bulldog' | 'Boxer'
color: 'brown' | 'white' | 'black'
}
type MyDog = LookUp<Cat | Dog, 'dog'> // expected to be `Dog`type LookUp<U, T extends string> = U extends { type: infer S } & Record<any, any> ? (S extends T ? U : never) : nevertype LookUp<U, T extends string> = U extends { type: T } ? U : nevertype trimed = TrimLeft<' Hello World '> // 应推导出 'Hello World 'type Space = ' ' | '\n' | '\t'
type TrimLeft<S extends string> = S extends `${Space}${infer NewS}` ? TrimLeft<NewS> : Stype trimed = Trim<' Hello World '> // expected to be 'Hello World'type Space = ' ' | '\n' | '\t'
type TrimLeft<S extends string> = S extends `${Space}${infer NewS}` ? TrimLeft<NewS> : S
type TrimRight<S extends string> = S extends `${infer NewS}${Space}` ? TrimLeft<NewS> : S
type Trim<S extends string> = TrimLeft<TrimRight<S>>type Space = ' ' | '\n' | '\t'
type Trim<S extends string> = S extends `${Space}${infer NewS}` | `${infer NewS}${Space}` ? Trim<NewS> : Stype capitalized = Capitalize<'hello world'> // expected to be 'Hello world'type MyCapitalize<S extends string> = S extends `${infer F}${infer Rest}` ? `${Uppercase<F>}${Rest}` : Stype replaced = Replace<'types are fun!', 'fun', 'awesome'> // 期望是 'types are awesome!'type Replace<S extends string, From extends string, To extends string> = S extends `${infer Pre}${From}${infer Post}`
? From extends ''
? S
: `${Pre}${To}${Post}`
: Stype replaced = ReplaceAll<'t y p e s', ' ', ''> // 期望是 'types'type ReplaceAll<S extends string, From extends string, To extends string> = S extends `${infer Pre}${From}${infer Post}`
? From extends ''
? S
: `${Pre}${To}${ReplaceAll<Post, From, To>}`
: Stype Fn = (a: number, b: string) => number
type Result = AppendArgument<Fn, boolean>
// 期望是 (a: number, b: string, x: boolean) => numbertype AppendArgument<Fn extends (...args: any) => any, A> = Fn extends (...args: infer Args) => infer R
? (...args: [...Args, A]) => R
: nevertype perm = Permutation<'A' | 'B' | 'C'> // ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']type Permutation<T, Rest = T> = [T] extends [never] ? [] : Rest extends Rest ? [T, ...Permutation<Exclude<T, Rest>>] : []type LengthOfString<S extends string, T extends string[] = []> = S extends `${string}${infer Rest}`
? LengthOfString<Rest, [string, ...T]>
: T['length']type flatten = Flatten<[1, 2, [3, 4], [[[5]]]]> // [1, 2, 3, 4, 5]type Flatten<Arr extends any[]> = Arr extends [infer Item, ...infer Rest]
? [...(Item extends any[] ? Flatten<Item> : [Item]), ...Flatten<Rest>]
: []type Test = { id: '1' }
type Result = AppendToObject<Test, 'value', 4> // expected to be { id: '1', value: 4 }type AppendToObject<T extends Record<string, any>, U extends string, V> = {
[Key in keyof T]: T[Key]
} & {
[key in U]: V
} extends infer Obj
? { [K in keyof Obj]: Obj[K] }
: nevertype AppendToObject<T, U extends keyof any, V> = {
[K in keyof T | U]: K extends keyof T ? T[K] : V
}