likes
comments
collection
share

TypeScript的基础、进阶、高级用法

作者站长头像
站长
· 阅读数 35

本文章内容较多,请耐心读完

基础数据类型

  • boolean 布尔

    const flag: boolean = false;
    
  • number 数值

    const count: number = 20;
    
  • string 字符串

    const content: string = '文本';
    
  • undefined & null

    • 默认情况下 null 和 undefined 是所有类型的子类型。 也就是说你可以把 null 和 undefined 赋值给其他类型
    • 如果tsconfig.json指定了"strictNullChecks":true ,即开启严格模式后, null 和 undefined 只能给它们自己的类型赋值
    • 但是 undefined 可以给 void 赋值

any

  • 表示任意类型,any会跳过类型检查器对值的检查,任何值都可以赋值给any类型的变量
    let anyVal: any = 1;
    anyVal = 'abc';
    anyVal = true;
    anyVal = () => {};
    

void

  • 一般表示函数无返回值
function voidFn(): void {
    console.log('没有返回值');
}

never

    1. 在函数声明过程中,一般表示函数永远不可能有返回值
    • 情况一:throw new Error
    function neverFn01(): never {
        throw new Error('error');
    }
    
    • 情况二:while 死循环
    function neverFn02(): never {
        // 情况二:死循环
        while (true) {}
    }
    
    1. 在运算过程中,never| 运算的幺元
type TNever001 = '1' | '2' | never;

// 编译:
// type TNever001 = "1" | "2"

unknown

  • 表示未知类型
  • 任何值都可以分配给 unknown
  • 但 unknown 只能分配给 unknown 和 any
let unknown001: unknown = 1;
unknown001 = true;
unknown001 = () => {};
const anyVal02: any = unknown001; // 正确
const unknown002: any = unknown001; // 正确
const string003: string = unknown001; // 报错

对象类型

  • object
    • object object 类型用于表示所有的非原始类型
    • 即我们不能把 number、string、boolean、symbol等 原始类型赋值给 object
    • 在严格模式下,null 和 undefined 类型也不能赋给 object
      let object: object;
      object = {}; // 编译正确
      object = 1; // 报错
      object = 'abc'; // 报错
      object = true; // 报错
      object = null; // 正确,但严格模式也报错
      object = undefined; // 正确,但严格模式也报错
      
  • Object
    • 大 Object 代表所有拥有 toString、hasOwnProperty 方法的类型
    • 所以所有原始类型、非原始类型都可以赋给 Object(严格模式下 null 和 undefined 不可以)
      let bigObject: Object;
      bigObject = {}; // 编译正确
      bigObject = 1; // 编译正确
      bigObject = 'abc'; // 编译正确
      bigObject = true; // 编译正确
      bigObject = null; // 正确,但严格模式也报错
      bigObject = undefined; // 正确,但严格模式也报错
      
  • {}
    • 空对象类型和大 Object 一样 也是表示原始类型和非原始类型的集合
      let emptyObj: {}; // 效果同上个案例
      

联合类型

  • 联合类型用|分隔,表示取值可以为多种类型中的一种
let statusCode: string | number;
statusCode = 200;
statusCode = '200';

字面量类型

type T001 = true
type T002 = 1
type T003 = 1 | 2 | 3
type T004 = 'get' | 'post'

复合类型 (set 和 map)

  • set 是指一个无序的、无重复元素的集合
    // 正向案例
    type Size = 'small' | 'default' | 'big' | 'large';
    
    // 如果写了重复的值
    type Size = 'small' | 'small' | 'default' | 'big' | 'large'; // 值会去重,所以与上面的声明一样
    
  • map 和JS中的对象一样,是一些没有重复键的键值对
    // 正向案例
    interface IA {
        a: string;
        b: number;
    }
    type TA =  {
        a: string;
        b: number;
    }
    // 如果写了重复的key,会提示报错
    

enum 枚举

  • enum在TS中出现的比较早,它引入了JavaScript没有的数据结构(编译成一个双向map)
  • 入侵了运行时,与TypeScript宗旨不符
  • 普通枚举
    enum Color01 {
      RED,
      BLUE,
      YELLOW,
    }
    const red: Color01 = Color01.RED;
    console.log(red); // 0
    
  • 设置初始值
    enum Color02 {
      RED = 2,
      BLUE,
      YELLOW,
    }
    const blue: Color02 = Color02.BLUE;
    console.log(blue); // 3
    
  • 字符串枚举
    enum Color03 {
      RED = '红色',
      BLUE = '蓝色',
      YELLOW = '黄色',
    }
    const yellow: Color03 = Color03.YELLOW;
    console.log(yellow); // 黄色
    
  • 常量枚举
    const enum Color04 {
      RED,
      BLUE,
      YELLOW,
    }
    const colors: Array<Color04> = [Color04.RED, Color04.BLUE, Color04.YELLOW];
    console.log(colors); // [0, 1, 2]
    

Array 数组

  • 第一种:普通语法方式
    const arr1: string[] = ['a', 'b', 'c'];
    
  • 第二种:泛型类型方式
    const arr2: Array<string> = ['a', 'b', 'c'];
    

Tuple 元祖

const tup: [string, number] = ['abc', 20];

class 类

  • class 此关键字用来定义类
class Person {
    name: string;
    age: number;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

function 函数

  • function 函数声明
function add01(x: number, y: number): number {
    return x + y;
}
  • function 函数表达式
const add02 = function (x: number, y: number): number {
    return x + y;
};
  • function 接口定义函数
interface IAdd03 {
    (x: number, y: number): number;
}
  • function 可选参数
const add04 = function (x: number, y?: number): number {
    return y ? x + y : x;
};
  • function 默认参数
const add05 = function (x: number, y: number = 20): number {
    return x + y;
};
  • function 剩余参数
const add06 = function (...counts: number[]): number {
    return counts.reduce((a, b) => a + b);
};
  • function 函数重载
    • 函数重载或方法重载是使用相同名称和不同参数数量或类型创建多个方法的一种能力
    • 函数重载真正执行的是同名函数最后定义的函数体
    • 在最后一个函数体定义之前全都属于函数类型定义
    • 不能写具体的函数实现方法 只能定义类型
function add07(x: number, y: number): number;
function add07(x: string, y: string): string;
function add07(x: any, y: any): any {
    return x + y;
}

类型推论

let count01 = 1;
count01 = true; // 报错,因为声明时已推论为数值类型
let count02;
count02 = true; // 正确编译,因为声明时没有赋值,所以推论为 any 类型
count02 = 'abc';

类型转换

let text: null | string = 'to be or not to be';
// 第一种:as写法 ( 此方法更常用,兼容性更好 )
let strLength001: number = (text as string).length;
// 第二种:尖括号写法
let strLength002: number = (<string>text).length;

非空断言

  • 作用是断言某个变量不会是null / undefined,告诉编译器停止报错
  • 此断言只在编译阶段有作用,运行时环境不受影响
let mightBeUndefined;
mightBeUndefined.a = 2; // Error:“mightBeUndefined”可能为“未定义”
mightBeUndefined!.a = 2; // 编译通过

let user: string | null | undefined;
console.log(user.toLocaleUpperCase()); // 错误
console.log(user!.toLocaleUpperCase()); // 正确

确定赋值断言

let value01: number;
console.log(value01); // 错误:不能在赋值前使用该变量
let value02!: number;
console.log(value02); // 正确

@ts-ignore

  • 用于忽略下一行的报错,尽量少用
data.b.c = 1; // Error:找不到名称“var1”
// @ts-ignore
data.b.c = 1; // 忽略报错(因为有上一行的指令)
data.b.c = 1; // 继续报错

类型别名

  • 类型别名用来给一个类型起个新名字。
  • 它只是起了一个新名字,并没有创建新类型。
  • 类型别名常用于联合类型。
type CountType = number | Array<number>;
function compute(value: CountType) {}
compute(20); // 正确
compute([10, 20]); // 正确
compute('abc'); // 错误

交叉类型

  • 交叉类型就是跟联合类型相反,用&操作符表示,交叉类型就是两个类型必须存在
interface IPersonA {
    name: string;
    age: number;
}
interface IPersonB {
    name: string;
    gender: string;
}
type PersonC = IPersonA & IPersonB;
let person: PersonC = {
    name: 'zs',
    age: 20,
    gender: '男',
};
  • 交叉类型取的多个类型的并集,但是如果key相同但是类型不同,则该key为never类型
interface IpersonA {
    name: string;
}
interface IpersonB {
    name: number;
}
function testAndFn(params: IpersonA & IpersonB) {}
testAndFn({ name: '张三' }); // 不能将类型“string”分配给类型“never”

interface 接口

interface IPerson {
    readonly name: string; // 只读
    age?: number; // 可选
}

索引访问操作符

使用 [] 操作符可以进行索引访问

interface Person6 {
    name: string;
    age: number;
}
type NameType = Person6['name']; // string
type AgeType = Person6['age']; // number

接口索引签名

  • 有时候我们希望一个接口中除了包含必选和可选属性之外,还允许有其他的任意属性,这时我们可以使用「 索引签名 」
  • 需要注意的是:一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
type NewType = number | string;
interface IPersonC {
    name: string;
    age: number;
    [prop: string]: NewType; // prop字段必须是 string类型 or number类型。 值是any类型或自定义类型
}

接口与类型别名的区别

  • 定义
    • interface: 接口的作用就是为这些类型命名和为你的代码或第三方代码定义数据模型
    • type: 类型别名会给类型(基本类型、联合类型、元组、手写类型)起一个新的名字,不会新建一个类型,只会是一种引用关系
  • 相同点:
      1. 接口和类型别名都可以用来描述对象或函数的类型,只是语法不同
      interface MyInterface1 {
          name: string;
          say(): void;
      }
      type MyTYpe1 = {
          name: string;
          say(): void;
      };
      
      1. 都允许扩展
      • interface 使用『 extends 』实现继承
      interface MyInterface2 {
          name: string;
      }
      interface MyInterface20 extends MyInterface2 {
          age: number;
      }
      let userI2: MyInterface20 = {
          name: 'zhangsan',
          age: 20,
      };
      
      • type 使用『 & 』交叉能力实现
      type MyType2 = {
          name: string;
      };
      type MyType22 = MyType2 & {
          age: number;
      };
      let userT2: MyType22 = {
          name: 'zhangsan',
          age: 20,
      };
      
  • 不同点:
      1. type可以声明基本数据类型别名/联合类型/元组等,而interface不行
      type MyType3 = number;
      type MyType4 = number | string;
      type MyType5 = MyType3 | MyType4;
      type MyType6 = [number, string, boolean];
      
      1. interface可以重载合并声明,而type不行
      interface MyInterface3 {
          name: string;
      }
      interface MyInterface3 {
          age: number;
      }
      let userI3: MyInterface3 = {
          // 此时,MyInterface3同名重载,拥有上面两个属性
          name: 'zhangsan',
          age: 20,
      };
      

泛型 基础使用

  • 泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
// 声明泛型公式
function getValue<T>(arg: T): T {
    return arg;
}
getValue<string>('树哥'); // 使用:定义T为string类型
getValue('树哥'); // 使用:自动推导类型为 string
  • 泛型 多个参数
function getValue2<T, U>(arg: [T, U]): [T, U] {
    return arg;
}
getValue2<string, number>(['树哥', 20]); // 定义类型
getValue(['树哥', 20]); // 自动推导类型
  • 泛型 泛型约束
function getLength<T>(arg: T): number {
    return arg.length; // 报错:因为不确定T的类型,所以不能调用length方法
}
interface LengthWise {
    length: number;
}
function getLength2<T extends LengthWise>(arg: T): number {
    return arg.length; // 编译正确
}
  • 泛型 泛型接口
interface IKeyValue<T, U> {
    key: T;
    value: U;
}
let kv01: IKeyValue<string, number> = {
    key: '年龄',
    value: 20,
};
let kv02: IKeyValue<string, string> = {
    key: '姓名',
    value: '张三',
};
  • 泛型 泛型类
class Person5<T> {
    value: T;
    add: (x: T, y: T) => T;
}
let user5 = new Person5<number>();
user5.value = 'abc'; // 报错,因为声明的类型是数值
user5.value = 5; // 正确编译
user5.add(3, 5); // 正确编译
  • 泛型 泛型类型别名
type DataType<T> = { list: T[] } | T[];
let data01: DataType<string> = { list: ['abc', 'def'] };
let data02: DataType<number> = [1, 2, 3];
  • 泛型 泛型默认类型
function createArray<T>(length: number, value: T): T[] {
    let result: Array<T> = [];
    for (let i = 0; i < length; i++) {
        result.push(value);
    }
    return result;
}
createArray<string>(30, '文本'); // 参数1是长度 参数2是内容

类型守卫(4个)

    1. typeof 关键字:基本类型检测
    • 用于获取一个“常量”的类型,这里的“常量”是指任何可以在编译期确定的东西,例如const、function、class等
    • 它是从 实际运行代码 通向 类型系统 的单行道
    • 理论上,任何运行时的符号名想要为类型系统所用,都要加上 typeof
    • 但是class 比较特殊不需要加,因为 ts 的 class 出现得比 js 早,现有的为兼容性解决方案
    • 写法只支持:typeof 'x' === 'typeName'typeof 'x' !== 'typeName'
    • x 必须是 'number' , 'string' , 'boolean' , 'symbol'
    // 案例一
    function testType(val: string | number): string {
        if (typeof val === 'number') return 'number';
        if (typeof val === 'string') return 'string';
        return '值为其它类型';
    }
    
    // 案例二
    const config = { width: 2, height: 2 };
    function getLength(str: string) {
        return str.length;
    }
    type TConfig = typeof config;
    // TConfig 的值:
    // type TConfig = {
    //   width: number;
    //   height: number;
    // }
    
    // 案例三
    type TGetLength = typeof getLength;
    // TGetLength 的值:
    // type TGetLength = (str: string) => number
    
    1. instanceof 关键字:复杂类型检测
    function createDate(val: Date | string): Date {
        if (val instanceof Date) {
            return val;
        } else {
            return new Date(val);
        }
    }
    
    1. in 关键字:检测子集
    interface InObj01 {
        name: string;
        x: string;
    }
    interface InObj02 {
        name: string;
        y: string;
    }
    function isIn(arg: InObj01 | InObj02): string {
        if ('x' in arg) return arg.x;
        if ('y' in arg) return arg.y;
    }
    isIn({ name: 'demo', x: 'xxx' }); // xxx
    isIn({ name: 'demo', x: 'yyy' }); // yyy
    
    1. is 关键字:类型谓词
    function isNumber(value: any): value is number {
        return typeof value === 'number';
    }
    function isString(value: any): value is string {
        return typeof value === 'string';
    }
    

泛型工具类型(5个)

    1. typeof 提取数据的类型

    此关键词除了做类型保护,还可以从实现推出类型

    let obj1 = {
        name: '张三',
        age: 20,
        isMan: true,
    };
    type PersonType = typeof obj1;
    function getPerson(value: PersonType): string {
        return value.name;
    }
    getPerson(obj1);
    
    1. keyof 取出 Map 结构中所有key

    可以用来获取一个对象接口中的所有 key 值

    interface IPersonD {
        name: string;
        age: number;
        sex: string;
    }
    type PersonKey = keyof IPersonD;
    function getVal(data: IPersonD, key: PersonKey) {
        return data[key];
    }
    let zsInfo = { name: 'zs', age: 20, sex: '男' };
    getVal(zsInfo, 'name'); // zs
    getVal(zsInfo, 'age'); // 20
    
    1. in 循环联合类型

    用来遍历 Set 结构

    type MyType8 = 'a' | 'b' | 'c';
    type DataInterface = {
        [P in MyType8]: string;
    };
    const data03: DataInterface = {
        a: 'aaa',
        b: 'bbb',
        c: 'ccc',
    };
    
    1. infer 提取类型
    • infer 的作用是让TypeScript自己推断,并将推断的结果存储到一个临时名字中,并且只能用于extends语句中
    • 它与泛型的区别在于,泛型是声明一个“参数”,而infer是声明一个“中间变量”
      // 案例一
      type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
      
      // 案例二
      type Unpacked<T> = T extends (infer U)[]
          ? U
          : T extends (...args: any[]) => infer U
          ? U
          : T extends Promise<infer U>
          ? U
          : T;
      type T0 = Unpacked<string>; // string
      type T1 = Unpacked<string[]>; // string
      type T2 = Unpacked<() => string>; // string
      type T3 = Unpacked<Promise<string>>; // string
      type T4 = Unpacked<Promise<string>[]>; // Promise<string>
      type T5 = Unpacked<Unpacked<Promise<string>[]>>; // string
      
    1. extends
      1. 继承能力
      interface ILengthWise {
          length: number;
      }
      function getLength3<T extends ILengthWise>(arg: T): number {
          return arg.length;
      }
      getLength3(3); // 报错:不具备length属性
      getLength3({ length: 10, name: '张三' }); // 编译正确
      
      1. 判断能力
      // 判断 B类型 是否继承自 A类型
      // 是的话 说明 B类型 是 A类型 的子类型
      // 也就是 B类型 完全包含 A类型
      
      type A = {
          name: string;
          age: number;
      };
      type B = {
          name: string;
          age: number;
          sex: string;
      };
      type C = B extends A ? '1' : '2'; // C的类型值为:'1'
      

内置工具类型(12个)

    1. Required
    • 场景:将所有属性转换为必选
    • 入参:Map结构
    • 返回:Map结构
    // 代码示例
    
    interface IPerson1 {
        name: string;
        age?: number;
        sex?: string;
    }
    let user1: Required<IPerson1>;
    user1 = { name: 'zs' }; // 报错,缺少其它两个属性
    user1 = { name: 'zs', age: 20, sex: '男' }; // 正常运行
    
    // 手写实现
    
    type Required<T> = { [P in keyof T]-?: T[P] }
    
    1. Partial
    • 场景:将所有属性转换为可选
    • 入参:Map结构
    • 返回:Map结构
    // 代码示例
    
    interface IPerson2 {
        name: string;
        age: number;
        sex: string;
    }
    let user2: Partial<IPerson2>;
    user2 = {}; // 正常运行
    user2 = { name: 'zs' }; // 正常运行
    
    // 手写实现
    
    type Partial<T> = { [P in keyof T]?: T[P] }
    
    1. Exclude<T, U>
    • 场景:从T 的属性中移除 U 的属性
    • 入参:Set结构
    • 返回:Set结构
    // 代码示例
    
    type T0 = Exclude<'a' | 'b' | 'c', 'a'>; // "b" | "c"
    type T1 = Exclude<'a' | 'b' | 'c', 'a' | 'b'>; // "c"
    type T2 = Exclude<string | number | (() => void), Function>; // string | number
    
    // 手写实现
    
    type Exclude<T, U> = T extends U ? never : T
    
    1. Extract<T, U>
    • 场景:从 T 中找到 U 的属性,然后组合返回
    • 入参:Set结构
    • 返回:Set结构
    // 代码示例
    
    type T3 = Extract<'a' | 'b' | 'c' | 'd', 'a' | 'c' | 'f'>; // "a" | "c"
    type T4 = Extract<string | number | (() => void), Function>; // () => void
    
    // 手写实现
    
    type Extract<T, U> = T extends U ? T : never
    
    1. Readonly
    • 场景:将类型属性转换为只读
    • 入参:Map结构
    • 返回:Map结构
    // 代码示例
    
    interface IPerson3 {
        name: string;
        age: number;
    }
    let user3: Readonly<IPerson3> = {
        name: 'zhangsan',
        age: 20,
    };
    user3.name = 'lisi'; // 报错:无法为“name”赋值,因为它是只读属性
    
    // 手写实现
    
    type Readonly<T> = { readonly [P in keyof T]: T[P] }
    
    1. Record<K extends keyof any, T> 将K中所有的属性的值转化为 T 类型
    • 场景:将K中所有的属性的值转化为 T 类型(Set转Map)
    • 入参:Set结构
    • 返回:Map结构
    // 代码示例
    
    type property = 'key01' | 'key02';
    type Type1 = Record<property, boolean>;
    let type01: Type1 = {
        // 编译正确
        key01: true,
        key02: true,
    };
    let type02: Type1 = {
        // 报错:属性必须为 boolean
        key01: 'abc',
        key02: 123,
    };
    
    // 手写实现
    
    type Record<K extends keyof any, T> = { [P in K]: T }
    
    1. Pick<T,U>
    • 场景:从 T 类型中挑出 U 的属性
    • 入参:<Map结构,Set结构>
    • 返回:Map结构
    // 代码示例 
    
    type Type05 = {
        name: string;
        age: number;
        sex: string;
    };
    type Type06 = Pick<Type05, 'name' | 'sex'>;
    let td06: Type06 = {
        name: 'zs',
        sex: '男',
    };
    
    // 手写实现
    
    type Pick<T, K extends keyof T> = { [P in K]: T[P] }
    
    1. Omit<T,U>
    • 场景:从 T 中取出除去 U 属性的其它属性
    • 入参:<Map结构,Set结构>
    • 返回:Map结构
    // 示例代码
    
    type Type07 = {
        name: string;
        age: number;
        sex: string;
    };
    type Type08 = Omit<Type07, 'name' | 'age'>;
    let td08: Type08 = { sex: '男' };
    
    // 手写实现
    
    type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
    
    1. NonNullable
    • 场景:去除类型中的 null 和 undefined
    • 入参:Set结构
    • 返回:Set结构
    // 示例代码
    
    type T01 = NonNullable<string | number | null>; // string | number
    type T02 = NonNullable<
        null | undefined | Function | Array<null> | Array<undefined>
    >; // Function | null[] | undefined[]
    
    // 手写实现
    
    type NonNullable<T> = T & {}
    
    1. ReturnType
    • 场景:获取函数返回值的类型
    • 入参:函数类型
    • 返回:函数返回值的类型
    // 代码示例
    
    type Func01 = (name: string) => string;
    let RT01: ReturnType<Func01> = 'abc'; // 编译正确
    let RT02: ReturnType<Func01> = 20; // 报错:类型不匹配
    
    // 手写实现
    
    type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any
    
    1. Parameters
    • 场景:用于获得函数的参数类型所组成的元组类型
    • 入参:函数类型
    • 返回:元组(函数参数的类型)
    // 代码示例
    
    type TP01 = Parameters<(name: string, age: number) => string>; // [string, number]
    let d01: TP01 = ['zs', 20]; // 正确编译
    let d02: TP01 = ['zs', '20']; // 报错:类型不正确
    
    // 手写实现
    
    type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
    
    1. InstanceType<T>
    • 场景:返回构造函数类型 T 的实例类型
    • 入参:类
    • 返回:类的类型
    // 代码示例
    
    class P09 {}
    type T09 = InstanceType<typeof P09>; // P09
    
    // 手写实现
    
    type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any
    

TypeScript v4.1版本后支持映射使用 as 关键字

type MyUser = {
    name: string;
    age: number;
    local: string;
};
type NewMyUser<T> = {
    [P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];
};
type Type100 = NewMyUser<MyUser>; 

// Type100的类型值如下:

// type Type100 = {
//     getName: () => string;
//     getAge: () => number;
//     getLocal: () => string;
// }

环境模块

    1. declare 声明
    declare const IS_MOBILE = true; // 编译后此行消失
    const wording = IS_MOBILE ? '移动端' : 'PC端';
    
    1. 三斜线指令
    /// <reference path="../typings/monaco.d.ts" />
    const range = new monaco.Range(2, 3, 6, 7);
    

类型的递归

  • TS原生的Readonly只会限制一层写入操作,我们可以利用递归来实现深层次的Readonly
  • 但要注意,TS对最大递归层数做了限制,最多递归5层
    type DeepReadonly<T> = {
        readonly [P in keyof T]: DeepReadonly<T[P]>;
    };
    interface SomeObject {
        a: {
            b: {
                c: number;
            };
        };
    }
    const obj01: Readonly<SomeObject> = { a: { b: { c: 2 } } };
    obj01.a.b.c = 3; // 编译通过
    const obj02: DeepReadonly<SomeObject> = { a: { b: { c: 2 } } };
    obj02.a.b.c = 3; // Error: 无法为“c”赋值,因为它是只读属性
    

小测试

interface SomeProps {
    a: string;
    b: number;
    c: (e: MouseEvent) => void;
    d: (e: TouchEvent) => void;
}
// 如何得到 'c' | 'd' ?
  • :
type CheckType<T, S> = {
    [K in keyof T]: T[K] extends S ? K : never;
}[keyof T];
type TRes01 = CheckType<SomeProps, Function>;

手写实现1:使用 extends 关键字,实现内置方法 NonNullable


type My_NonNullable<T> = T extends null | undefined ? never : T;
type n01 = My_NonNullable<string | number>; // string | number
type n02 = My_NonNullable<string | null | undefined>; // string
let d02: n02 = null; // 严格模式报错

手写实现2:分配式extends,实现DiffFilter

type Diff<T, U> = T extends U ? never : T; // 拆解,返回T的差集
type Filter<T, U> = T extends U ? T : never; // 拆解,返回T的交集
type Type01 = 'a' | 'b' | 'c' | 'd' | 'e';
type Type02 = 'a' | 'e' | 'x' | 'y' | 'z';
let diff01: Diff<Type01, Type02>; // "b" | "c" | "d"
let filter01: Filter<Type01, Type02>; // "a" | "e"

手写实现3:实现内置类型 ReturnType

/**
 * infer
 * infer X 就相当于声明了一个变量,这个变量随后可以使用,有点像for循环里面的声明语句
 * 不同的是,infer X的这个位置本应该有一个写死的类型变量,只不过用infer R替换了,更灵活
 * 需要注意的是infer声明的这个变量只能在true分支中使用
 */
 
// 实现内置 ReturnType
type My_ReturnType<T> = T extends (...arg: any[]) => infer R ? R : any;

手写实现4:实现一个 isString 类型

type isString<T> = T extends string ? true : false;
type I0 = isString<number>; // false
type I1 = isString<11>; // false
type I2 = isString<string>; // true
type I3 = isString<'11'>; // true
type I4 = isString<any>; // boolean
type I5 = isString<never>; // never

手写实现5:实现一个类型判断

type TypeName<T> = T extends string
    ? 'string'
    : T extends number
    ? 'number'
    : T extends boolean
    ? 'boolean'
    : T extends null
    ? 'null'
    : T extends undefined
    ? 'undefined'
    : T extends Function
    ? 'function'
    : 'object';
type T0 = TypeName<123>; // "number"
type T1 = TypeName<'abc'>; // "string"
type T2 = TypeName<true>; // "boolean"
type T3 = TypeName<null>; // 非严格也是 "string"
type T4 = TypeName<undefined>; // 非严格也是 "string"
type T5 = TypeName<{}>; // "object"
// 这里返回的是联合类型,因为传入的是联合类型,所以会被拆解进行三元运算
type T6 = TypeName<123 | 'abc'>; // "string" | "number"

手写实现6:实现几个提取能力

  • 提取方法的 key
  • 提取方法的 key 和 value
  • 提取属性中除去方法所有的 key
  • 提取属性中除去方法所有的 key 和 value
// 基础数据模型
interface User {
    id: number;
    name: string;
    age: number;
    updateName(value: string): void;
    updateAge(value: number): void;
}

// @method 提取方法的key
type FunctionPropertyNames<T> = {
    [P in keyof T]: T[P] extends Function ? P : never;
}[keyof T];
type result01 = FunctionPropertyNames<User>; // "updateName" | "updateAge"

// @method 提取方法的key和value
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;
type result02 = FunctionProperties<User>; // { updateName: (value: string) => void; updateAge: (value: number) => void;}

// @method 提取属性中除去方法所有的key
type NonFunctionPropertyNames<T> = {
    [P in keyof T]: T[P] extends Function ? never : P;
}[keyof T];
type result03 = NonFunctionPropertyNames<User>; // "id" | "name" | "age"

// @method 提取属性中除去方法所有的key和value
type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;
type result04 = NonFunctionProperties<User>; // {id: number;name: string;age: number;}
转载自:https://juejin.cn/post/7223658167493787709
评论
请登录