所有页面
由 GitBook 提供支持
1 / 5

Loading...

Loading...

Loading...

Loading...

Loading...

TypeScript

TypeScript入门

【第二届青训营-寒假前端场】- 「TypeScript入门」笔记

什么是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版本发布

为什么是TypeScript

动态类型在执行过程中进行类型的匹配,js的弱类型会在执行时进行隐式类型转换,而在静态类型中则不然

TypeScript则为静态类型:java、c/c++等

  • 可读性增强:基于语法解析TSDoc,ide增强

  • 可维护性增强:在编译阶段暴露大部分错误

  • 多人合作的大型项目中,可以获得更好的稳定性和开发效率

TypeScript是JS的超集

  • 包含于兼容所有Js特性, 支持共存

  • 支持渐进式引入与升级

基本语法

基本数据类型

js ==> ts

可以看到,ts的类型定义方式:let 变量名: 类型 = 值;

对象类型

函数类型

js:

ts:

可以看到,格式为function 函数名(参数:类型...):返回值类型

函数重载

简化形式如下:

数组类型

type作用就是给类型起一个新名字,相当于c++中的typedef

TypeScript补充类型

  • 空类型:表示无赋值

  • 任意类型:是所有类型的子类型

  • 枚举类型:支持枚举值到枚举名的正、反向映射

Typescript泛型

泛型,之前学过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 出现在类型推荐中,表示定义类型变量

工程应用

TypeScript工程应用——Web

  1. 配置webapack loader相关配置

  2. 配置tsconfig.js文件(宽松——严格,都可以定义)

  3. 运行webpack启动/ 打包

  4. loader处理ts文件时, 会进行编译与类型检查

相关loader:

  1. or

TypeScript工程应用——Node

使用TSC编译

  1. 安装Node与npm

  2. 配置tsconfig.js文件

  3. 使用npm安装tsc

  4. 使用tsc运行编译得到js文件

总结感想

这节课老师讲了TypeScript的用处与基本语法、和JS的对比、高级类型的应用,后续也深入讲了一下类型保护与类型守卫,在最后总结了TypeScript如何在工程中进行应用。TypeScript作为JS的一个超集,他增加了类型检查的功能,可以在编译阶段就将代码中的错误暴露出来,这是js这类动态类型所不具备的,在多人合作的大型项目中,使用TS往往可以获得更好的稳定性和开发效率。

本文引用的大部分内容来自林皇老师的课以及ts官方文档~

配合泛型P, 即表示每个key
  • 关键字 ? ,通过设定对象可选选项,即可自动推导出子集类型

  • ,可以用于
    指代类型

    infer 简单示例如下:

    在这个条件语句 T extends (...args: infer P) => any ? P : T 中,infer P 表示待推断的函数参数。

    整句表示为:如果 T 能赋值给 (...args: infer P) => any,则结果是 (...args: infer P) => any 类型中的参数 P,否则返回为 T。

    • 在这里就相当于把这个函数返回值类型指代为R

    TypeScript基础类型
    接口 · TypeScript中文网
    函数 · TypeScript中文网
    基础类型 · TypeScript中文网
    reduce()
    高级类型
    awesome-typescript-loader
    babel-loader
    image.png
    image.png
    image.png
    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: string
    interface 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表示,使用在类型推荐命中的结果中

    TypeScript 类型体操练习

    想玩玩儿类型体操不?很有意思的哦

    前不久一位朋友,在面试中用体操写出 Flat,把我给震惊到了,了解后就觉得 ts 的类型体操相当有趣

    题目来自于大名鼎鼎的体操仓库:type-challenges

    学习来自于大名鼎鼎的掘金小册:TypeScript 类型体操通关秘籍 不得不说是一本很不错的小册

    其他了解:青训营 |「TypeScript 入门」笔记

    题目列表及题解

    热身 (1)

    简单 (13)

    中等 (72)

  • 13・Hello World
    4・实现 Pick
    7・实现 Readonly
    11・元组转换为对象
    14・第一个元素
    2・获取函数返回类型
    3・实现 Omit
    8・Readonly 2
    9・深度 Readonly

    Easy题(13/13)

    Easy 题共13道,是真的都很easy

    题单

    解答

    4・实现 Pick

    通过 keyof 取出 T 类型的所有键,如果只是 MyPick<T, K>的话,会报错如下

    7・实现 Readonly

    https://tsch.js.org/11/play/zh-CN

    11・元组转换为对象

    14・第一个元素

    符合小册中模式匹配做提取套路,通过 extends 对类型参数做匹配,结果保存到通过 infer 声明的局部类型变量里,若匹配就能从该局部变量里拿到提取出的类型。

    18・获取元组长度

    会抛出 Error 说明得限制传入的类型

    43・Exclude

    这题可能看的人一头雾水,但是可以看看这里:

    也就是分别判断 T 中的每个联合类型,是否 extends U

    189・Awaited

    这里用到了 这个类型

    268・If

    这题也比较基础,考的就是 extends

    533・Concat

    在类型系统里实现 JavaScript 内置的 Array.concat 方法,这个类型接受两个参数,返回的新数组类型应该按照输入参数从左到右的顺序合并为一个新的数组。

    898・Includes

    在类型系统里实现 JavaScript 的 Array.includes 方法,这个类型接受两个参数(左边参数是否 include 右边参数),返回的类型要么是 true 要么是 false。

    运用了小册中的套路三:递归复用做循环,

    3057・Push

    3060・Unshift

    3312・Parameters

    11・元组转换为对象
    14・第一个元素
    18・获取元组长度
    43・Exclude
    189・Awaited
    268・If
    533・Concat
    898・Includes
    3057・Push
    3060・Unshift
    3312・Parameters
    接受挑战
    我的解答
    接受挑战
    我的解答
    接受挑战
    我的解答
    接受挑战
    我的解答
    接受挑战
    我的解答
    接受挑战
    我的解答
    Distributive Conditional Types
    接受挑战
    我的解答
    PromiseLike
    接受挑战
    我的解答
    接受挑战
    我的解答
    接受挑战
    我的解答
    接受挑战
    我的解答
    接受挑战
    我的解答
    接受挑战
    我的解答
    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 : never
    type Length<T extends readonly unknown[]> = T['length']
    type MyExclude<T, U> = T extends U ? never : T
    type 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
      : never
    type If<C extends boolean, T, F> = C extends true ? T : F
    type 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>
      : false
    type Push<T, U> = T extends [...infer Rest] ? [...Rest, U] : never
    type Unshift<T, U> = T extends [...infer Rest] ? [U, ...Rest] : never
    type MyParameters<T extends (...args: any[]) => any> = T extends (...args: infer A) => any ? A : never

    Middle(20/72)

    Middle题共72道,是类型体操的大头

    题单(中等)

    解答

    2・获取函数返回类型

    不使用 ReturnType 实现 TypeScript 的 ReturnType<T> 泛型。

    例如:

    正确解答:

    注意参数数组~

    3・实现 Omit

    不使用 Omit 实现 TypeScript 的 Omit<T, K> 泛型。

    Omit 会创建一个省略 K 中字段的 T 对象。

    例如:

    正确解答:

    乍一看很绕,其实搞懂 keyof 和 in 就明白了。

    8・Readonly 2

    实现一个通用 MyReadonly2<T, K>,它带有两种类型的参数 T和 K。

    K指定应设置为 Readonly 的 T的属性集。如果未提供 K,则应使所有属性都变为只读,就像普通的 Readonly<T>一样。

    例如

    正确解答

    结合上面的 和简单题中的 可得出该题的答案。

    9・深度 Readonly ⭐

    实现一个通用的 DeepReadonly<T>,它将对象的每个参数及其子对象递归地设为只读。 您可以假设在此挑战中我们仅处理对象。数组,函数,类等都无需考虑。但是,您仍然可以通过覆盖尽可能多的不同案例来挑战自己。

    例如

    正确解答

    递归解决,注意特判 Funtion 的情况

    10・元组转合集

    实现泛型 TupleToUnion<T>,它返回元组所有值的合集。

    例如

    解答: 没想太多的递归实现 👇

    翻了翻 issue 发现的另一个实现,确实哦!

    12・可串联构造器 ⭐⭐

    • 我的解答

    在 JavaScript 中我们经常会使用可串联(Chainable/Pipeline)的函数构造一个对象,但在 TypeScript 中,你能合理的给它赋上类型吗?

    在这个挑战中,你可以使用任意你喜欢的方式实现这个类型 - Interface, Type 或 Class 都行。你需要提供两个函数 option(key, value) 和 get()。在 option 中你需要使用提供的 key 和 value 扩展当前的对象类型,通过 get 获取最终结果。

    例如

    你只需要在类型层面实现这个功能 - 不需要实现任何 TS/JS 的实际逻辑。

    你可以假设 key 只接受字符串而 value 接受任何类型,你只需要暴露它传递的类型而不需要进行任何处理。同样的 key 只会被使用一次。

    正确解答:注意 Key 必须为之前没取过的,或是 Value 类型与之前不同的(用例 2 和 3)。

    15・最后一个元素

    实现一个通用 Last<T>,它接受一个数组 T并返回其最后一个元素的类型。

    正确解答:基本送分题

    16・出堆

    实现一个通用 Pop<T>,它接受一个数组 T,并返回一个由数组 T的前 length-1 项以相同的顺序组成的数组。

    例如

    额外:同样,您也可以实现 Shift,Push和 Unshift吗?

    正确解答:看了小册后不难做出

    20・Promise.all ⭐⭐⭐

    键入函数 PromiseAll,它接受 PromiseLike 对象数组,返回值应为 Promise<T>,其中 T是解析的结果数组。

    正确解答:

    疑惑吗?疑惑就对了,请看看这个 pr :

    62・Type Lookup ⭐⭐

    有时,您可能希望根据某个属性在联合类型中查找类型。

    在此挑战中,我们想通过在联合类型 Cat | Dog中搜索公共 type字段来获取相应的类型。换句话说,在以下示例中,我们期望 LookUp<Dog | Cat, 'dog'>获得 Dog,LookUp<Dog | Cat, 'cat'>获得 Cat。

    正确解答:第一反应如下

    翻了翻 issue,很巧妙地如下:

    106・Trim Left

    实现 TrimLeft<T> ,它接收确定的字符串类型并返回一个新的字符串,其中新返回的字符串删除了原字符串开头的空白字符串。

    例如

    正确解答:非常经典的!

    108・Trim ⭐

    实现Trim<T>,它是一个字符串类型,并返回一个新字符串,其中两端的空白符都已被删除。

    例如

    正确解答:结合上一题,易得 TrimRight 的实现方法,二者结合即可实现 Trim

    也可以用:

    110・Capitalize

    实现 Capitalize<T> 它将字符串的第一个字母转换为大写,其余字母保持原样。

    例如

    正确解答:还是通过模式匹配,使用内置类型 Uppercase 进行大写转换

    116・Replace ⭐

    实现 Replace<S, From, To> 将字符串 S 中的第一个子字符串 From 替换为 To 。

    例如

    正确解答:注意 From 为空的情况

    119・ReplaceAll ⭐⭐

    实现 ReplaceAll<S, From, To> 将一个字符串 S 中的所有子字符串 From 替换为 To。

    例如

    正确解答:在上题基础上,递归!

    191・追加参数

    实现一个泛型 AppendArgument<Fn, A>,对于给定的函数类型 Fn,以及一个任意类型 A,返回一个新的函数 G。G 拥有 Fn 的所有参数并在末尾追加类型为 A 的参数。

    正确解答:显而易见,本题 easy

    296・Permutation ⭐⭐⭐⭐

    实现联合类型的全排列,将联合类型转换成所有可能的全排列数组的联合类型。

    正确解答:题目字越少,难度越大(x)

    本题实现全排列,同时要利用联合类型的分散传参这个特性

    翻 issue 的时候翻到了这个优秀的解答:

    [T] extends [never] 是由于 never 无法被准确判断,任何extends never的条件语句都会返回never,而我们需要返回的是空数组,在小册中的 有提到这一点。

    298・Length of String ⭐

    计算字符串的长度,类似于 String#length 。

    正确解答:不讲武德版当然就是 type LengthOfString<S extends string> = S['length'] 当然,讲武德版就是这样 ⬇️

    459・Flatten ⭐⭐⭐

    在这个挑战中,你需要写一个接受数组的类型,并且返回扁平化的数组类型。

    例如:

    正确解答:经典的数组拍平,需要注意的是...展开需在外层,Item extends any[] ? ...Flatten<Item> : Item 是不行的......

    527・Append to object ⭐

    • 实现一个为接口添加一个新字段的类型。该类型接收三个参数,返回带有新字段的接口类型。

    例如:

    正确解答:先将交叉类型整出来,再把交叉类型遍历一遍。

    看到一种很有趣的

    8・Readonly 2
    9・深度 Readonly
    10・元组转合集
    12・可串联构造器
    15・最后一个元素
    16・出堆
    20・Promise.all
    62・Type Lookup
    106・Trim Left
    108・Trim
    110・Capitalize
    116・Replace
    119・ReplaceAll
    191・追加参数
    296・Permutation
    298・Length of String
    459・Flatten
    527・Append to object
    接受挑战
    我的解答
    接受挑战
    我的解答
    接受挑战
    我的解答
    实现 Omit
    实现 Readonly
    接受挑战
    我的解答
    接受挑战
    我的解答
    接受挑战
    接受挑战
    我的解答
    接受挑战
    我的解答
    接受挑战
    我的解答
    Variadic tuple types
    接受挑战
    我的解答
    一个解答
    接受挑战
    我的解答
    模式提取做匹配
    接受挑战
    我的解答
    第二种解法
    接受挑战
    我的解答
    接受挑战
    我的解答
    接受挑战
    我的解答
    接受挑战
    我的解答
    接受挑战
    我的解答
    Distributive Conditional Types
    TS 类型体操笔记 - 296 Permutation
    套路六:特殊特性要记清
    接受挑战
    我的解答
    接受挑战
    我的解答
    接受挑战
    我的解答
    解法
    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 : never
    interface 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 // OK
    type 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> : never
    type 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 : never
    type 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) : never
    type LookUp<U, T extends string> = U extends { type: T } ? U : never
    type trimed = TrimLeft<'  Hello World  '> // 应推导出 'Hello World  '
    type Space = ' ' | '\n' | '\t'
    type TrimLeft<S extends string> = S extends `${Space}${infer NewS}` ? TrimLeft<NewS> : S
    type 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> : S
    type capitalized = Capitalize<'hello world'> // expected to be 'Hello world'
    type MyCapitalize<S extends string> = S extends `${infer F}${infer Rest}` ? `${Uppercase<F>}${Rest}` : S
    type 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}`
      : S
    type 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>}`
      : S
    type Fn = (a: number, b: string) => number
    
    type Result = AppendArgument<Fn, boolean>
    // 期望是 (a: number, b: string, x: boolean) => number
    type AppendArgument<Fn extends (...args: any) => any, A> = Fn extends (...args: infer Args) => infer R
      ? (...args: [...Args, A]) => R
      : never
    type 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] }
      : never
    type AppendToObject<T, U extends keyof any, V> = {
      [K in keyof T | U]: K extends keyof T ? T[K] : V
    }