likes
comments
collection
share

【灌篮高手】教练,我想学TS类型体操啊

作者站长头像
站长
· 阅读数 61
【灌篮高手】教练,我想学TS类型体操啊

逆变、协变、双向协变

  • ts是结构型类型系统,抽象的是父类型,具体的是子类型
  • 协变: 允许子类型转换为父类型(类属性、接口、联合类型、函数返回类型)
  • 逆变: 允许父类型转换为子类型。 (只有函数参数这里是逆变的)
  • 双向协变:父子类型的值可以互相赋值(ts配置strictFunctionTypes为false则函数参数可以双向协变)
type Father = { // 更抽象是父类型
    name: string;
    age: number;
}
type Son = { // 更具体是子类型
    name: string;
    age: number;
    like: string;
}
type FatherType = 1 | 2 | 3 // 更抽象是父类型
type SonType = 1 | 2 // 更具体是子类型
let FatherFn = (p:Father)=>{
    console.log(`我的名字是${p.name},我的年龄是${p.age}`)
}

let SonFn = (p:Son)=>{
    console.log(`我的爱好是${p.like}`)
}

// SonFn = FatherFn // 可以赋值

FatherFn = SonFn // 不可以赋值,会报错
// 假设执行FatherFn = SonFn了,就会变成下面这样,声明和传入的是Father类型,执行时访问了Father没有的属性like导致报错
const FatherFn: (p:Father)=>void = (p)=>{
    console.log(`我的爱好是${p.like}`)
}

infer推断

  • infer只能在extends条件判断时使用
  • infer推导的名称相同并且都处于协变的位置,则推导的结果将会是联合类型
type Foo<T> = T extends {
  a: infer U;
  b: infer U;
} ? U : never;

// type T1 = string
type T1 = Foo<{ a: string; b: string }>;

// type T2 = string | number
type T2 = Foo<{ a: string; b: number }>;
  • infer推导的名称相同并且都处于逆变的位置,则推导的结果将会是交叉类型
type Bar<T> = T extends {
  a: (x: infer U) => void;
  b: (x: infer U) => void;
} ? U : never;

// type T1 = string
type T1 = Bar<{ a: (x: string) => void; b: (x: string) => void }>;

// type T2 = never
type T2 = Bar<{ a: (x: string) => void; b: (x: number) => void }>;
  • 索引重映射: as

as 用于修改映射类型的 key。

// 通过索引查询 keyof,索引访问 t[k],索引遍历 in,索引重映射 as,返回全新的 key、value 构成的新的映射类型
type MapType<T> = {
    [
    Key in keyof T
    as `new key is ${Key & string}`
    ]: [T[Key], T[Key], T[Key]]
}
// type res = {
//     "new key is a": [1, 1, 1];
//     "new key is b": [2, 2, 2];
// }
type res = MapType<{ a: 1, b: 2 }>
  • 条件:T extends U ? X : Y

条件判断和 js 逻辑相同,都是如果满足条件就返回 X 否则返回 Y。

// 条件:extends ? :
// 如果 T 是 2 的子类型,那么类型是 true,否则类型是 false。
type isTwo<T> = T extends 2 ? true : false;
// false
type res = isTwo<1>;

函数类型重载

  • 方法名相同,参数不同(个数,类型,位置),返回类型无所谓
function fn(s1: string): void
function fn(n1: number, s1: string): void
function fn(x: any, y?: any): void { // 具体实现写在最后面
  console.log(x)
  console.log(y)
}
fn('abc')
fn(1, 'xyz')

declare类型重定义

  • Dom 和 Js内置类型等,可以通过tsconfig.json的compilerOptions的lib添加;npm包类型通过添加@types/xxx包解决,还可以自己加包 DefinitelyTyped
//global.d.ts
// 声明同名模块
declare module 'axios' {
  export type aaa = string;
  export type bbb = number;
  export interface AxiosRequestConfig {
    ccc: string;
    ddd: number;
  }
}

【灌篮高手】教练,我想学TS类型体操啊

【灌篮高手】教练,我想学TS类型体操啊

所有内置类型

// 获取函数参数类型
type Parameters<T extends (...args: any) => any> 
    = T extends (...args: infer P) => any 
        ? P 
        : never;
// 获取函数返回值类型        
type ReturnType<T extends (...args: any) => any> 
    = T extends (...args: any) => infer R 
        ? R 
        : any;
 // 获取构造函数参数类型
type ConstructorParameters<
    T extends abstract new (...args: any) => any
> = T extends abstract new (...args: infer P) => any 
    ? P 
    : never;
// 获取构造函数的实例类型
type InstanceType<
    T extends abstract new (...args: any) => any
> = T extends abstract new (...args: any) => infer R 
    ? R 
    : any;
// 获取this的类型    
type ThisParameterType<T> = 
    T extends (this: infer U, ...args: any[]) => any 
        ? U 
        : unknown;    
// 去掉函数的this类型
type OmitThisParameter<T> = 
    unknown extends ThisParameterType<T> 
        ? T 
        : T extends (...args: infer A) => infer R 
            ? (...args: A) => R 
            : T;   
// 变为可选             
type Partial<T> = {
    [P in keyof T]?: T[P];
};
// 变为必填
type Required<T> = {
    [P in keyof T]-?: T[P];
};
// 变为只读
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};
// 创建索引类型
type Record<K extends keyof any, T> = {
    [P in K]: T;
};
// 选取索引类型的部分类型
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};
// 删除索引类型的部分类型
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;


// 去掉联合类型的一部分类型
type Exclude<T, U> = T extends U ? never : T;
// 保留联合类型的一部分类型
type Extract<T, U> = T extends U ? T : never;
// 递归获取异步类型的返回类型
type Awaited<T> =
    T extends null | undefined
        ? T 
        : T extends object & { then(onfulfilled: infer F): any }
            ? F extends ((value: infer V, ...args: any) => any)
                ? Awaited<V>
                : never 
            : T;
// 不是null或者undefine
type NonNullable<T> = T extends null | undefined ? never : T;
// 变成全大写
type Uppercase<S extends string> = intrinsic;
// 变成全小写
type Lowercase<S extends string> = intrinsic;
// 变成首字母大写
type Capitalize<S extends string> = intrinsic;
// 变成首字母大写
type Uncapitalize<S extends string> = intrinsic;

判断是否是可选属性

// Eg2 = false
type Eg2 = {} extends {key1: string} ? true : false;
// Eg3 = true
type Eg3 = {} extends {key1?: string} ? true : false;

type和interface区别

  • interface一般用户对外暴露类型,而type一般用于类型别名
  • type 使用 & 实现扩展,interface 用 extends 来实现扩展
  • type可以声明基本数据类型别名/联合类型/元组等,而interface不行
  • interface能够合并声明,而type不行

鸭子辩型

  • 像鸭子一样走路并且嘎嘎叫的就叫鸭子,即具有鸭子特征的认为它就是鸭子,也就是通过制定规则来判定对象是否实现这个接口。
interface LabeledValue {
  label: string;
}
function printLabel(labeledObj: LabeledValue) {
  console.log(labeledObj.label);
}
let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj); // OK

tsconfig.json配置学习

  • npm install -g typescript 全局安装 ts
  • tsc --build 编译ts文件生成类型文件
  • tsc --watch 监听ts文件变化热更新类型文件
{
   // 指定需要编译文件,包含其下的目录。 否则默认当前目录下除了exclude之外的所有.ts, .d.ts,.tsx 文件。与当前tsconfig.json文件的相对路径
   "include": ["./src/**/*.ts"],
   // 指定需要编译文件名列表,具体到那个文件。 否则默认当前目录下除了exclude之外的所有.ts, .d.ts,.tsx 文件
   "files": [ "./src/app.ts","./src/helper.ts"],
   // 不编译某些文件
   "exclude": ["node_modules", "**/node_modules/*", "**/*.spec.ts"],
   "compilerOptions": {
       // 只编译修改过的文件,这个时候会生成tsconfig.tsbuildinfo,下次编译的时候会进行对比只编译修改过的文件 
       "incremental": true,
       // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
       "target": "es6",
       // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015' 'esnext'
       "module": "esnext",
       /* 注意:如果未指定--lib,则会注入默认的librares列表。注入的默认库为:
       对于 --target ES5: DOM,ES5,ScriptHost
       对于 --target ES6: DOM,ES6,DOM.Iterable,ScriptHost
       TS 绝不会在您的代码中注入polyfill,所以需要你自己制定编译lib */
       "lib": ["es5", "dom", "ScriptHost", "es2015.promise"],
       // 允许编译JS
       "allowJs": true,
       /* 是否检测JS的语法,例如下面的语法编辑器会报错
       let name = 'paul';
       console.log(name.a.b) */
       "checkJs": true,
       // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
       "jsx": preserve,
       /* 如果设为true,编译每个ts文件之后会生成一个js文件和一个声明文件,
       declaration和allowJs不能同时设为true */
       "declaration": true
       // 值为true或false,指定是否为声明文件.d.ts生成map文件
       "declarationMap": true
       // 用来指定编译时是否生成.map文件
       "sourceMap": true,
       // 当module设置为 'amd' and 'system'的时候可以使用此命令,这样可以将ts文件打包到一个目录下
       "outFile":"./",
       //  outDir 编译后的文件存到到哪个目录下,默认是每一个ts文件的当前目录,,下面的配置就是把ts编译到dist目录下
       "outDir": './dist',
       // 是否编译构建引用项目,很复杂后面介绍
       "composite": true,
       // 指定文件用来存储增量编译信息,默认是tsconfig.tsbuildinfo
       "tsBuildInfoFile": "./",
       // 编译的时候删除注释
       "removeComments": true,
       // 不生成编译文件,这个一般比较少用,这个build目录下将没有任何文件,但是会进行编译,有错误会抛出
       "noEmit": true,
       // 是否引入npm包tslib中的辅助函数,__extends等 
       "importHelpers": true,
       // 当target为'ES5' or 'ES3'时,为'for-of', spread, and destructuring'中的迭代器提供完全支持
       "downlevelIteration": true,
       // isolatedModules的值为true或false,指定是否将每个文件作为单独的模块,默认为true,它不可以和declaration同时设定
       // 不是很理解,将每一个文件作为单独模块
       "isolatedModules": true,
       /* Strict Type-Checking Options */
       // 严格模式将会打开下面的几个选项
       "strict": false, 
       /* 不允许变量或函数参数具有隐式any类型,例如
       function(name) {
           return name;
       } */
       "noImplicitAny": true,
       // null类型检测,const teacher: string = null;会报错
       "strictNullChecks": true,
       // 对函数参数进行严格逆变比较
       "strictFunctionTypes": true,
       // 严格检查bind call apply 之后的类型是否正确
       "strictBindCallApply": true,
       // 此规则将验证构造函数内部初始化前后已定义的属性。
       "strictPropertyInitialization": true,
       // 检测this是否隐式指定
       "noImplicitThis": true,
       // 使用js的严格模式,在每一个文件上部声明 use strict
       "alwaysStrict": true,
       /* Additional Checks */
       // 默认false,是否检测定义了但是没使用的变量
       "noUnusedLocals": true,
       // 用于检查是否有在函数体中没有使用的参数
       "noUnusedParameters": true,
       // 用于检查函数是否有返回值,设为true后,如果函数没有返回值则会提示
       "noImplicitReturns": true,
       // 用于检查switch中是否有case没有使用break跳出switch
       "noFallthroughCasesInSwitch": true,
       /* Module Resolution Options */
       // 用于选择模块解析策略,有'node'和'classic'两种类型
       "moduleResolution": "node",
       // 复杂的很 下面单独介绍这三个模块
       "baseUrl": './'
       "paths": {},  
       // 这src1 src2两个目录合并编译并导出到同一个目录dist中             
       "rootDirs": ["./src1", "./src2"]],
       /* typeRoots用来指定声明文件或文件夹的路径列表,如果指定了此项,则只有在这里列出的声明文件才会被加载 */
       typeRoots: [],
       // types用来指定需要包含的模块,只有在这里列出的模块的声明文件才会被加载进来
       types:[],
       // 用来指定允许从没有默认导出的模块中默认导入 。为false的时候导入模块必须是 import * as xxx from xxx 模式
       "allowSyntheticDefaultImports": true, 
       // 通过为导入内容创建命名空间,实现CommonJS和ES模块之间的互操作性
       "esModuleInterop": true ,
       // 不把符号链接解析为真实路径,具体可以了解下webpack和node.js的symlink相关知识
       "preserveSymlinks": true,
       "allowUmdGlobalAccess": true,
       
       // sourceRoot用于指定调试器应该找到TypeScript文件而不是源文件的位置,这个值会被写进.map文件里
       "sourceRoot": '',
       // mapRoot用于指定调试器找到映射文件而非生成文件的位置,指定map文件的根路径,该选项会影响.map文件中的sources属性
       "mapRoot",
       // inlineSourceMap指定是否将map文件内容和js文件编译在一个同一个js文件中,如果设为true,则map的内容会以//#soureMappingURL=开头,然后接base64字符串的形式插入在js文件底部
       "inlineSourceMap": true,
       // inlineSources用于指定是否进一步将ts文件的内容也包含到输出文件中
       "inlineSources": true,
       
       // experimentalDecorators用于指定是否启用实验性的装饰器特性
       "experimentalDecorators": true,
       
       // emitDecoratorMetadata用于指定是否为装上去提供元数据支持,关于元数据,也是ES6的新标准,可以通过Reflect提供的静态方法获取元数据,如果需要使用Reflect的一些方法,需要引用ES2015.Reflect这个库
       "emitDecoratorMetadata": true,
       // compileOnSave的值是true或false,如果设为true,在我们编辑了项目中的文件保存的时候,编辑器会根据tsconfig.json中的配置重新生成文件,不过这个要编辑器支持
       "compileOnSave": true,
       // monorepo项目中,子项目common和pc有个性化的tsconfig配置文件,在全局tsconfig引入子项目的配置
       "references":[
           {"path": "../../libs/common"},
           {"path": "../../libs/pc"},
       ]",
   }
}

  • tsconfig.json配置调试(示例:strictBindCallApply 可以在 playground 调试,不想关掉这个检查可以换成下面的...和reduce结合的方法)

【灌篮高手】教练,我想学TS类型体操啊【灌篮高手】教练,我想学TS类型体操啊

  • baseUrl和paths 连用时,如果webpack设置了alias则paths不生效,此时需要将paths和alias设置一样才生效

【灌篮高手】教练,我想学TS类型体操啊【灌篮高手】教练,我想学TS类型体操啊

类型体操练习室

  • playground 请尽情的联系类型体操,直到魔功大成。

【灌篮高手】教练,我想学TS类型体操啊

下面开始类型体操上强度了, 不行就算了,不要勉强自己。🚫🚫🚫🚫

模式匹配做提取


// 推断Promise的返回值类型
type GetValueType<P> = P extends Promise<infer Value> ? Value : never;

type GetValueResult = GetValueType<Promise<'guang'>>;

// 获取数组第一个元素的类型
type GetFirst<Arr extends unknown[]> = Arr extends [infer First, ...unknown[]] ? First : never;

type GetFirstResult = GetFirst<[1,2,3]>;
type GetFirstResult2 = GetFirst<[]>;

// 获取数组最后一个元素的类型
type GetLast<Arr extends unknown[]> = Arr extends [...unknown[], infer Last] ? Last : never;

type GetLastResult = GetLast<[1,2,3]>;
type GetLastResult2 = GetLast<[]>;

// 去掉数组最后一个元素
type PopArr<Arr extends unknown[]> = Arr extends [] ? [] : Arr extends [...infer Rest, unknown] ? Rest : never;

type PopResult = PopArr<[1,2,3]>;
type PopResult2 = PopArr<[]>;

// 去掉数组第一个元素
type ShiftArr<Arr extends unknown[]> = Arr extends [] ? [] : Arr extends [unknown, ...infer Rest] ? Rest : never;

type ShiftResult = ShiftArr<[1,2,3]>;
type ShiftResult2 = ShiftArr<[]>;

// Str是否以Prefix开头
type StartsWith<Str extends string, Prefix extends string> = Str extends `${Prefix}${string}` ? true : false;

type StartsWithResult = StartsWith<'guang and dong', 'guang'>;
type StartsWithResult2 = StartsWith<'guang and dong', 'dong'>;

// 把Str中的from替换成to
type ReplaceStr<Str extends string, From extends string, To extends string> =  Str extends `${infer Prefix}${From}${infer Suffix}` ? `${Prefix}${To}${Suffix}` : Str;

type ReplaceResult = ReplaceStr<"Guangguang's best friend is ?", "?", "Dongdong">;
type ReplaceResult2 = ReplaceStr<"abc", "?", "Dongdong">;

// 去掉字符串前后所有空格
type TrimStrRight<Str extends string> = Str extends `${infer Rest}${' ' | '\n' | '\t'}` ? TrimStrRight<Rest> : Str;

type TrimRightResult = TrimStrRight<'guang        '>;

type TrimStrLeft<Str extends string> = Str extends `${' ' | '\n' | '\t'}${infer Rest}` ? TrimStrLeft<Rest> : Str;

type TrimLeftResult = TrimStrLeft<'      dong'>;

type TrimStr<Str extends string> =TrimStrRight<TrimStrLeft<Str>>;

type TrimResult = TrimStr<'      dong   '>;

// 获取函数参数类型
type GetParameters<Func extends Function> = Func extends (...args: infer Args) => unknown ? Args : never;

type ParametersResult = GetParameters<(name: string, age: number) => string>;
type ParametersResult2 = GetParameters<() => string>;

// 获取函数返回值类型
type GetReturnType<Func extends Function> = Func extends (...args: any[]) => infer ReturnType ? ReturnType : never;

type ReturnTypeResullt = GetReturnType<(name: string) => 'dong'>;

// 获取类的this类型
class Dong {
    name: string;

    constructor() {
        this.name = "dong";
    }

    hello(this: Dong) {
        return 'hello, I'm ' + this.name;
    }
}

const dong = new Dong();
dong.hello();

dong.hello.call({xxx:1});

type GetThisParameterType<T> = T extends (this: infer ThisType, ...args: any[]) => any ? ThisType : unknown;

type GetThisParameterTypeRes = GetThisParameterType<typeof dong.hello>;

// 获取构造函数的实例类型
type GetInstanceType<ConstructorType extends new (...args: any) => any> 
    = ConstructorType extends new (...args: any) => infer InstanceType 
        ? InstanceType 
        : any;

interface Person {
    name: string;
}

interface PersonConstructor {
    new(name: string): Person;
}

type GetInstanceTypeRes = GetInstanceType<PersonConstructor>;

// 获取构造函数参数类型
type GetConstructorParameters<
    ConstructorType extends new (...args: any) => any
> = ConstructorType extends new (...args: infer ParametersType) => any
    ? ParametersType
    : never;

type GetConstructorParametersRes = GetConstructorParameters<PersonConstructor>;

// 获取ref属性的类型
type GetRefProps<Props> = 
    'ref' extends keyof Props
        ? Props extends { ref?: infer Value | undefined}
            ? Value
            : never
        : never;

type GetRefPropsRes = GetRefProps<{ ref?: 1, name: 'dong'}>;

type GetRefPropsRes2 = GetRefProps<{ ref?: undefined, name: 'dong'}>;

重新构造做变换

// 数组追加一个类型
type Push<Arr extends  unknown[], Ele> = [...Arr, Ele];

type PushResult = Push<[1, 2, 3], 4>;

// 数组头部加一个类型
type Unshift<Arr extends  unknown[], Ele> = [Ele, ...Arr];

type UnshiftResult = Unshift<[1, 2, 3], 0>;

// 把两个数组下标相同的元素放在一起组成新的数组,返回最后的二维数组类型
type Zip<One extends [unknown, unknown], Other extends [unknown, unknown]> = 
    One extends [infer OneFirst, infer OneSecond]
        ? Other extends [infer OtherFirst, infer OtherSecond]
            ? [[OneFirst, OtherFirst], [OneSecond, OtherSecond]] :[] 
                : [];

                
type ZipResult = Zip<[1,2], ['guang', 'dong']>;

// 递归实现
type Zip2<One extends unknown[], Other extends unknown[]> = 
    One extends [infer OneFirst, ...infer OneRest]
        ? Other extends [infer OtherFirst, ...infer OtherRest]
            ? [[OneFirst, OtherFirst], ...Zip2<OneRest, OtherRest>]: []
                : [];

type Zip2Result = Zip2<[1,2,3,4,5], ['guang', 'dong', 'is', 'best', 'friend']>;

// 字符串首字母大写
type CapitalizeStr<Str extends string> = Str extends `${infer First}${infer Rest}` ? `${Uppercase<First>}${Rest}` : Str;

type CapitalizeResult = CapitalizeStr<'guang'>;

// 字符串下划线变小驼峰
type CamelCase<Str extends string> = 
    Str extends `${infer Left}_${infer Right}${infer Rest}`
        ? `${Left}${Uppercase<Right>}${CamelCase<Rest>}`
        : Str;

type CamelCaseResult = CamelCase<'dong_dong_dong'>;

// 去掉子串
type DropSubStr<Str extends string, SubStr extends string> = 
    Str extends `${infer Prefix}${SubStr}${infer Suffix}` 
        ? DropSubStr<`${Prefix}${Suffix}`, SubStr> : Str;

type DropResult = DropSubStr<'dong~~~', '~'>;


// 追加函数参数
type AppendArgument<Func extends Function, Arg> = 
    Func extends (...args: infer Args) => infer ReturnType 
        ? (...args: [...Args, Arg]) => ReturnType : never;

type AppendArgumentResult  = AppendArgument<(name: string) => boolean, number>;


// 遍历对象属性
type Mapping<Obj extends object> = { 
    [Key in keyof Obj]: [Obj[Key], Obj[Key], Obj[Key]]
}

type res = Mapping<{ a: 1, b: 2}>;

// 对象所有属性变首字母大写
type UppercaseKey<Obj extends object> = { 
    [Key in keyof Obj as Uppercase<Key & string>]: Obj[Key]
}

type UppercaseKeyResult = UppercaseKey<{ guang: 1, dong: 2}>;

// ToReadonly
type ToReadonly<T> =  {
    readonly [Key in keyof T]: T[Key];
}

type ReadonlyResult = ToReadonly<{
    name: string;
    age: number;
}>;

// ToPartial
type ToPartial<T> = {
    [Key in keyof T]?: T[Key]
}

type PartialResult = ToPartial<{
    name: string;
    age: number;
}>;

// 范型属性变成可变类型
type ToMutable<T> = {
    -readonly [Key in keyof T]: T[Key]
}

type MutableResult =  ToMutable<{
    readonly name: string;
    age: number;
}>;

// ToRequired
type ToRequired<T> = {
    [Key in keyof T]-?: T[Key]
}

type RequiredResullt = ToRequired<{
    name?: string;
    age: number;
}>;

// 过滤出所有值类型是ValueType的属性
type FilterByValueType<Obj extends Record<string, any>, ValueType> = {
    [Key in keyof Obj 
        as Obj[Key] extends ValueType ? Key : never]
        : Obj[Key]
}

interface Person {
    name: string;
    age: number;
    hobby: string[];
}

type FilterResult = FilterByValueType<Person, string | number>;

递归复用做循环

// 递归获取Promise异步的返回值
type DeepPromiseValueType<P extends Promise<unknown>> =
    P extends Promise<infer ValueType> 
        ? ValueType extends Promise<unknown>
            ? DeepPromiseValueType<ValueType>
            : ValueType
        : never;

type DeepPromiseResult = DeepPromiseValueType<Promise<Promise<Record<string, any>>>>;

type DeepPromiseValueType2<T> = 
    T extends Promise<infer ValueType> 
        ? DeepPromiseValueType2<ValueType>
        : T;

type DeepPromiseValueType2Res = DeepPromiseValueType2<Promise<Promise<Promise<number>>>>;

// 反转数组类型
type ReverseArr<Arr extends unknown[]> = 
    Arr extends [infer First, ...infer Rest] 
        ? [...ReverseArr<Rest>, First] 
        : Arr;

type ReverseArrResult = ReverseArr<[1,2,3,4,5]>;

// 数组是否包含某类型
type Includes<Arr extends unknown[], FindItem> = 
    Arr extends [infer First, ...infer Rest]
        ? IsEqual<First, FindItem> extends true
            ? true
            : Includes<Rest, FindItem>
        : false;

type IsEqual<A, B> = (A extends B ? true : false) & (B extends A ? true : false);

type IncludesResult = Includes<[1, 2, 3, 4, 5], 4>;

type IncludesResult2 = Includes<[1, 2, 3, 4, 5], 6>;

// 删除数组内所有Item类型
type RemoveItem<Arr extends unknown[], Item, Result extends unknown[] = []> = 
    Arr extends [infer First, ...infer Rest]
        ? IsEqual<First, Item> extends true
            ? RemoveItem<Rest, Item, Result>
            : RemoveItem<Rest, Item, [...Result, First]>
        : Result;

type RemoveItemResult = RemoveItem<[1,2,2,3], 2>;

// 创建长度Length的unknown数组
type BuildArray<Length extends number, 
    Ele = unknown, 
    Arr extends unknown[] = []> =
    Arr['length'] extends Length 
        ? Arr 
        : BuildArray<Length, Ele, [...Arr, Ele]>;

type BuildArrResult = BuildArray<5>;

// 替换字符串中所有的From变成To
type ReplaceAll<Str extends string, 
    From extends string, 
    To extends string> = 
        Str extends `${infer Left}${From}${infer Right}`
            ? `${Left}${To}${ReplaceAll<Right, From, To>}`
            : Str;

type ReplaceAllResult = ReplaceAll<'guang guang guang', 'guang', 'dong'>;

// 字符串的每个字符变成联合类型
type StringToUnion<Str extends string> = 
    Str extends `${infer First}${infer Rest}`
        ? First | StringToUnion<Rest>
        : never;

type StringToUnionResult = StringToUnion<'hello'>;

// 反转字符串类型
type ReverseStr<Str extends string, 
    Result extends string = ''> = 
    Str extends `${infer First}${infer Rest}` 
        ? ReverseStr<Rest, `${First}${Result}`> 
        : Result;

type ReverseStrResult = ReverseStr<'hello'>;

// 递归变成只读属性
type DeepReadonly<Obj extends Record<string, any>> =
    Obj extends any
        ? {
            readonly [Key in keyof Obj]:
                Obj[Key] extends object
                    ? Obj[Key] extends Function
                        ? Obj[Key] 
                        : DeepReadonly<Obj[Key]>
                    : Obj[Key]
        }
        : never;

type obj = {
    a: {
        b: {
            c: {
                f: () => 'dong',
                d: {
                    e: {
                        guang: string
                    }
                }
            }
        }
    }
}

type DeepReadonlyResult = DeepReadonly<obj>;

数组长度做计数

// 创建长度为Length的数组
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 AddResult = Add<32, 25>;

// 数组实现减法
type Subtract<Num1 extends number, Num2 extends number> = 
    BuildArray<Num1> extends [...arr1: BuildArray<Num2>, ...arr2: infer Rest]
        ? Rest['length']
        : never;

type SubtractResult = Subtract<33, 12>;

// 数组实现乘法(n*m,累加m次n)
type Mutiply<Num1 extends number, Num2 extends number, Result extends unknown[] = []> =
    Num2 extends 0 ? Result['length']
        : Mutiply<Num1, Subtract<Num2, 1>, [...BuildArray<Num1>, ...Result]>;

type MutiplyResult = Mutiply<3, 222>;

// 数组实现除法(n/m,累减m次n)
type Divide<Num1 extends number, Num2 extends number, CountArr extends unknown[] = []> =
    Num1 extends 0 ? CountArr['length']
        : Divide<Subtract<Num1, Num2>, Num2, [unknown, ...CountArr]>;

type DivideResult = Divide<30, 5>;

// 获取字符串长度
type StrLen<
    Str extends string,
    CountArr extends unknown[] = []
> = Str extends `${string}${infer Rest}` ? StrLen<Rest, [...CountArr, unknown]> : CountArr['length']

type StrLenResult = StrLen<'Hello World'>;

// Num1大于Num2判断(数组长度从0开始递增,先到达某个数,则某个数更小)
type GreaterThan<
    Num1 extends number,
    Num2 extends number,
    CountArr extends unknown[] = []
> = Num1 extends Num2 
    ? false
    : CountArr['length'] extends Num2
        ? true
        : CountArr['length'] extends Num1
            ? false
            : GreaterThan<Num1, Num2, [...CountArr, unknown]>;

type GreaterThanResult = GreaterThan<3, 4>;

type GreaterThanResult2 = GreaterThan<6, 4>;

// 斐波那契数列累加
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>;

// 1、1、2、3、5、8、13、21、34
type FibonacciResult = Fibonacci<8>;

联合分发可简化

// Camelcase
type Camelcase<Str extends string> = 
    Str extends `${infer Left}_${infer Right}${infer Rest}`
    ? `${Left}${Uppercase<Right>}${Camelcase<Rest>}`
    : Str;

type CamelcaseResult = Camelcase<'aa_aa_aa'>;

type CamelcaseArr<
  Arr extends unknown[]
> = Arr extends [infer Item, ...infer RestArr]
  ? [Camelcase<Item & string>, ...CamelcaseArr<RestArr>]
  : [];

type CamelcaseArrResult = CamelcaseArr<['aa_aa_aa', 'bb_bb_bb', 'cc_cc_cc']>;

type CamelcaseUnion<Item extends string> = 
  Item extends `${infer Left}_${infer Right}${infer Rest}` 
    ? `${Left}${Uppercase<Right>}${CamelcaseUnion<Rest>}` 
    : Item;

type CamelcaseUnionResult = CamelcaseUnion<'aa_aa_aa' | 'bb_bb_bb' | 'cc_cc_cc'>;

// 是否是联合类型
type IsUnion<A, B = A> =
    A extends A
        ? [B] extends [A]
            ? false
            : true
        : never

type IsUnionResult = IsUnion<'a'|'b'|'c'|'d'>;

type IsUnionResult2 = IsUnion<[ 'a' | 'b' | 'c']>;


type TestUnion<A, B = A> = A  extends A ? { a: A, b: B} : never;

type TestUnionResult = TestUnion<'a' | 'b' | 'c'>;

// 转BEM命名风格
type BEM<
    Block extends string,
    Element extends string[],
    Modifiers extends string[]
> = `${Block}__${Element[number]}--${Modifiers[number]}`;

type bemResult = BEM<'guang', ['aaa', 'bbb'], ['warning', 'success']>;

// 组合类型
type Combination<A extends string, B extends string> =
    | A
    | B
    | `${A}${B}`
    | `${B}${A}`;
// 联合类型求所有组合类型(所有子集)
type AllCombinations<A extends string, B extends string = A> = A extends A
    ? Combination<A, AllCombinations<Exclude<B, A>>>
    : never;

type AllCombinationsResult = AllCombinations<'A' | 'B' | 'C'>;

特殊逻辑记清楚

  • [] [1,2,3] 都是元祖, number[]才是数组
  • any 类型与任何类型的交叉都是 any,也就是 1 & any 结果是 any
  • never 用于extends中,如果条件类型左边是类型参数,并且传入的是 never,那么直接返回 never
  • 元组类型的 length 是数字字面量,而数组的 length 是 number
  • 可选索引的值为 undefined 和值类型的联合类型
  • 索引签名不能构造成字符串字面量类型,因为它没有名字,而其他索引可以
  • keyof 只能拿到 class 的 public 索引,private 和 protected 的索引会被忽略
  • as const 之后推导出来的类型是带有 readonly 修饰的字面量,在x extends ?的时候要加上 x extends readonly ?
// 是否是any类型
type IsAny<T> = 'dong' extends ('guang' & T) ? true : false

type IsAnyResult = IsAny<any>;

type IsAnyResult2 = IsAny<'guang'>;

// 是否相等
type IsEqual<A, B> = (A extends B ? 1 : 2) & (B extends A ? 1 : 2);

type IsEqualRes = IsEqual<'a', any>;

// 源码里面是个hack的判断方式
type IsEqual2<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2)
    ? true : false;

type IsEqual2Res = IsEqual2<'a', any>;

// 是否联合类型
type IsUnion<A, B = A> =
    A extends A
        ? [B] extends [A]
            ? false
            : true
        : never

type IsUnionResult = IsUnion<'A' | 'B'>;
type IsUnionResult2 = IsUnion<'A'>;

// 是否是never
type IsNever<T> = [T] extends [never] ? true : false

type IsNeverResult = IsNever<never>;
type IsNeverResult2 = IsNever<any>;

type TestAny<T> = T extends number ? 1 : 2;

type TestAnyRes = TestAny<any>;

// 元组的length是数字字面量,而数组的length是number类型
type len = [1,2,3]['length'];

type len2 = number[]['length']

type IsTuple<T> = 
    T extends [...params: infer Eles] 
        ? NotEqual<Eles['length'], number> 
        : false;

type NotEqual<A, B> = 
    (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2)
        ? false : true;

type IsTupleResult = IsTuple<[1, 2, 3]>;

type IsTupleResult2 = IsTuple<number[]>;

// 联合转交叉
type UnionToIntersection<U> = 
    (U extends U ? (x: U) => unknown : never) extends (x: infer R) => unknown
        ? R
        : never;

type UnionToIntersectionResult = UnionToIntersection<{ guang: 1 } | { dong: 2 }>;

// 获取所有的可选类型( {} extends {a?:1} 为true  )
type GetOptional<Obj extends  Record<string, any>> = {
    [
        Key in keyof Obj 
            as {} extends Pick<Obj, Key> ? Key : never
    ] : Obj[Key];
}

type GetOptionalResult = GetOptional<{
  name: string;
  age?: number;
}>;

// 是否是必选类型
type isRequired<Key extends keyof Obj, Obj> = 
    {} extends Pick<Obj, Key> ? never : Key;

type GetRequired<Obj extends Record<string, any>> = { 
    [Key in keyof Obj as isRequired<Key, Obj>]: Obj[Key] 
}

type GetRequiredResult = GetRequired<{
  name: string;
  age?: number;
}>;

// 删除索引类型 (索引类型不是字符串子类型)
type RemoveIndexSignature<Obj extends Record<string, any>> = {
  [
      Key in keyof Obj 
          as Key extends `${infer Str}`? Str : never
  ]: Obj[Key]
}

type RemoveIndexSignatureResult = RemoveIndexSignature<{
  [key: string]: any;
  sleep(): void;
}>;

// 获取类的public类型
class Dong {
  public name: string;
  protected age: number;
  private hobbies: string[];

  constructor() {
    this.name = 'dong';
    this.age = 20;
    this.hobbies = ['sleep', 'eat'];
  }
}

type ClassPublicPropsResult = ClassPublicProps<Dong>;

type ClassPublicProps<Obj extends Record<string, any>> = {
    [Key in keyof Obj]: Obj[Key]    
}

// as const 会把自动推断的类型变成数字字面量类型
const obj = {
    a: 1,
    b: 2
}

type objType = typeof obj;

const arr = [1, 2, 3]

type arrType = typeof arr;

const obj2 = {
    a: 1,
    b: 2
} as const;

type objType2 = typeof obj2;

const arr2 = [1, 2, 3] as const;

type arrType2 = typeof arr2;
// as const 之后会变成 readonly类型,所以需要extends readonly
type ReverseArr<Arr> = Arr extends readonly [infer A, infer B, infer C] ? [C, B, A] : never;

type ReverseArrRes = ReverseArr<arrType2>;

其他题目

// 将2个数字按下标两两组成新数组,最终返回二维数组
type Zip<One extends unknown[], Other extends unknown[]> = 
  One extends [infer OneFirst, ...infer Rest1]
    ? Other extends [infer OtherFirst, ...infer Rest2]
      ? [[OneFirst, OtherFirst], ...Zip<Rest1, Rest2>]
      : []
    : [];

type Mutable<Obj> = {
  -readonly [Key in keyof Obj]: Obj[Key];
};

function zip(target: unknown[], source: unknown[]): unknown[];

function zip<Target extends readonly unknown[], Source extends readonly unknown[]>(
  target: Target,
  source: Source
): Zip<Mutable<Target>, Mutable<Source>>;

function zip(target: unknown[], source: unknown[]) {
  if (!target.length || !source.length) return [];

  const [one, ...rest1] = target;
  const [other, ...rest2] = source;

  return [[one, other], ...zip(rest1, rest2)];
}

const result = zip([1, 2, 3] as const, [4, 5, 6] as const);

const arr1 = [1, 2, 3];
const arr2 = [4, '5', 6];

const result2 = zip(arr1, arr2);







// 深递归添加索引签名
type Data = {
    aaa: number;
    bbb: {
        ccc: number;
        ddd: string;
    },
    eee: {
        fff: string;
        ddd: number;
    }
}
type DeepRecord<Obj extends Record<string, any>> = {
    [Key in keyof Obj]: 
        Obj[Key] extends Record<string, any>
            ? DeepRecord<Obj[Key]> & Record<string, any>
            : Obj[Key]
} & Record<string, any>;

type res = DeepRecord<Data>;

const data: Data = {
    aaa: 1,
    bbb: {
        ccc: 1,
        ddd: 'aaa'
    },
    eee: {
        fff: 'bbb',
        ddd: 2
    }
}




// 所有属性只有一个是'desc' | 'asc',其他都是false
type GenerateType<Keys extends keyof any> = {
    [Key in Keys]: {
        [Key2 in Key]: 'desc' | 'asc'
    } & {
        [Key3 in Exclude<Keys, Key>]: false
    }
}[Keys];

type res = GenerateType<'aaa' | 'bbb' | 'ccc'>;

const a:res = {
    aaa: 'asc',
    bbb: false,
    ccc: false
}

const b: res = {
    aaa: false,
    bbb: 'desc',
    ccc: false
}

const c: res = {
    aaa: 'asc',
    bbb: 'desc',
    ccc: false
}

做的不爽的看这里!!!!

🚫🚫🚫 这里还有更多题库: github.com/type-challe… 🚫🚫🚫 保证让您做到爽为止

学习参考资料

转载自:https://juejin.cn/post/7226567602373935164
评论
请登录