TS进阶

进阶

面向对象编程思想

  1. TS的伪重载
    TS可以在函数前面声明重载签名 ,使得在调用函数时推断出函数的返回类型,但是由于它不能重复实现同一个函数,而仅仅是对其的签名进行重载,因此称为伪重载

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function func(foo: number, bar: true): string
    function func(foo: number, bar?: false): number

    function func(foo: number, bar?: boolean): string | number {
    if (bar) {
    return String(foo);
    } else {
    return foo * 20;
    }
    }

    const res1 = func(599); // number
    const res2 = func(599, true); // string
    const res3 = func(599, false); // number
  2. 修饰符

    1. public:此类成员在类、类的实例、子类中都能被访问。
    2. private:此类成员仅能在类的内部被访问。
    3. protected:此类成员仅能在类与子类中被访问,可以将类和类的实例当成两种概念,即一旦实例化完毕,那就和类没关系了,即 不允许再访问受保护的成员
  3. 基类和派生类
    **确保在派生类中复写的方法在基类中存在 使用override修饰符 **修饰要复写的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 基类和派生类 
    class Base {
    print() {

    }
    }
    class Derived extends Base {
    override print() {

    }
    }
  4. 单一功能原则
    一个类应该仅具有一种职责

    这也意味着只存在一种原因使得需要修改类的代码。如对于一个数据实体的操作,其读操作和写操作也应当被视为两种不同的职责,并被分配到两个类中。更进一步,对实体的业务逻辑和对实体的入库逻辑也都应该被拆分开来

  5. 开放封闭原则

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    enum LoginType {
    WeChat,
    TaoBao,
    TikTok,
    }

    abstract class LoginHandler {
    abstract handler(): void
    }

    class WeChatLoginHandler implements LoginHandler {
    handler(): void {
    console.log(LoginType[0]);
    }
    }

    class TikTokLoginHandler implements LoginHandler {
    handler(): void {
    console.log(LoginType[1]);
    }
    }

    class TaoBaoLoginHandler implements LoginHandler {
    handler(): void {
    console.log(LoginType[2]);
    }
    }

    class Login {
    private static handleMap: Record<LoginType, LoginHandler> = {
    [LoginType.WeChat]: new WeChatLoginHandler(),
    [LoginType.TikTok]: new TikTokLoginHandler(),
    [LoginType.TaoBao]: new TaoBaoLoginHandler(),
    }

    public static handler(type: LoginType) {
    Login.handleMap[type].handler()
    }
    }

    Login.handler(LoginType.WeChat)
  6. 里氏替换原则
    一个派生类可以在程序的任何一处对其基类进行替换

    即子类完全继承了父类的一切,对父类进行了功能地扩展(而非收窄)

  7. 接口分离原则
    类的实现方应当只需要实现自己需要的那部分接口

    比如微信登录支持指纹识别,支付宝支持指纹识别和人脸识别,这个时候微信登录的实现类应该不需要实现人脸识别方法才对。这也就意味着我们提供的抽象类应当按照功能维度拆分成粒度更小的组成

  8. 依赖倒置原则
    这是 实现开闭原则的基础 ,它的核心思想即是 对功能的实现应该依赖于抽象层 ,即不同的逻辑通过实现不同的抽象类。

    还是登录的例子,我们的登录提供方法应该基于共同的登录抽象类实现(LoginHandler),最终调用方法也基于这个抽象类,而不是在一个高阶登录方法中去依赖多个低阶登录提供方

  9. 反射 在程序运行时去检查以及修改程序行为

断言

  1. 确保联合类型变量被妥善处理

    1. 对联合类型的每种类型进行处理,如果对联合类型新增了一种类型Function,同样会被判定为never,通过下面的方法正确处理。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    declare const strOrNumOrBool: string | number | boolean;

    // 不严谨的处理
    if (typeof strOrNumOrBool === "string") {
    console.log("str!");
    } else if (typeof strOrNumOrBool === "number") {
    console.log("num!");
    } else if (typeof strOrNumOrBool === "boolean") {
    console.log("bool!");
    } else {
    throw new Error(`Unknown input type: ${strOrNumOrBool}`);
    }

    // 通过never的判断正确处理
    if (typeof strOrNumOrBool === "string") {
    // 一定是字符串!
    strOrNumOrBool.charAt(1);
    } else if (typeof strOrNumOrBool === "number") {
    strOrNumOrBool.toFixed();
    } else if (typeof strOrNumOrBool === "boolean") {
    strOrNumOrBool === true;
    } else {
    const _exhaustiveCheck: never = strOrNumOrBool;
    throw new Error(`Unknown input type: ${_exhaustiveCheck}`);
    }
  2. 非空断言

    1
    2
    3
    4
    5
    const element = document.querySelector("#id"); // const element: Element | null
    const element = document.querySelector("#id")!; // const element: Element

    const target = [1, 2, 3, 599].find(item => item === 599); // const target: number | undefind
    const target = [1, 2, 3, 599].find(item => item === 599)!; // const target: number
  3. 类型断言

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 两种书写断言的方式
    interface IStruct {
    foo: string;
    bar: {
    barPropA: string;
    barPropB: number;
    barMethod: () => void;
    baz: {
    handler: () => Promise<void>;
    };
    };
    }
    const struct = {}
    struct as IStruct

    const istruct = <IStruct>{}

类型工具

  1. 基本使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    typeMaybeNull<T> = T | null;

    functionfun(input: MaybeNull<{ handler: () => {} }>) {
    input?.handler()
    }

    // 函数泛型
    typeMaybeArray<T> = T | T[];

    function ensureArray<T>(input: MaybeArray<T>): T[] {
    returnArray.isArray(input) ? input : [input];
    }
  2. 索引类型

    1. 索引签名类型
    1
    2
    3
    4
    5
    6
    interface AllStringTypes {
    [key: string]: string;
    }

    type PropType1 = AllStringTypes['linbudu']; // string
    type PropType2 = AllStringTypes['599']; // string
    1. 索引类型查询 keyof
      它可以将对象中的所有键转换为对应字面量类型,然后再组合成联合类型
    2. 索引类型访问
      1
      2
      3
      4
      5
      interface Foo {
      propA: number;
      }

      typePropAType = Foo['propA']; // type PropAType = number
  3. 类型守卫

    1
    2
    3
    4
    5
    6
    7
    8
    let res = 123;
    function assertIsNumber(val: any): asserts val is number {
    if (typeof val !== 'number') {
    throw new Error('Not a number!');
    }
    }
    assertIsNumber(res);
    res.toFixed();

内置工具类型及实现

  • 修饰工具类型

    • Partial
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      type MyParital<T> = {
      [K in keyof T]+?: T[K]
      }

      interface TestType {
      name: string,
      age: number,
      male: boolean
      }

      const res_Parital: TestType = {
      name: "",
      age: 18,
      male: true
      }
    • Require
      1
      2
      3
      4
      5
      type Required<T> = {
      [P in keyof T]-?: T[P];
      };

      // -?表示如果有问号就去掉问号
    • Readonly
      1
      2
      3
      type Readonly<T> = {
      +readonly [K in keyof T]: T[K]
      }
    • MarkPropsAsOptional 部分修饰
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      interface Res {
      code: 10000 | 10001 | 50000;
      status: "success" | "failure";
      data: any;
      }

      type MarkPropsAsOptional<T extends object, U extends keyof T> = Partial<Pick<T, U>> & Omit<T, U>
      type Flutten<T> = {
      [K in keyof T]: T[K]
      }
      type res_MarkProps = Flutten<MarkPropsAsOptional<Res, "code" | 'data'>>

      // type res_MarkProps = {
      // code?: 10000 | 10001 | 50000;
      // data?: any;
      // status: "success" | "failure";
      // }
  • 结构工具类型

    • Record
      1
      2
      3
      type Record<K extends keyof any, T> = {
      [P in K]: T
      }
    • Pick
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      // 从对象身上提取属性的类型
      typePick<T, K extends keyof T> = {
      [P in K]: T[P]
      }

      interface Foo {
      name: string;
      age: number;
      hello: (this: Foo) => string;
      }

      type PickedFoo = Pick<Foo, "name" | "hello">
      // type PickedFoo = {
      // name: string;
      // hello: (this: Foo) => string;
      // }
    • Omit
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      // 剔除T在U中有的类型 --> 差集
      typeExclude<T, U> = T extends U ? never : T;

      type Tmp1 = Exclude<1, 2>; // 1
      type Tmp2 = Exclude<1 | 2, 2>; // 1
      type Tmp3 = Exclude<1 | 2 | 3, 2 | 3>; // 1
      type Tmp4 = Exclude<1 | 2 | 3, 2 | 4>; // 1 | 3

      // 剔除对象身上对应属性类型
      typeOmit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
  • 集合工具类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 并集
    exporttypeConcurrence<A, B> = A | B;

    // 交集
    exporttypeIntersection<A, B> = A extends B ? A : never;

    // 差集
    exporttypeDifference<A, B> = A extends B ? never : A;

    // 补集
    exporttypeComplement<A, B extends A> = Difference<A, B>;
    //补集基于差集实现,我们只需要约束集合 B 为集合 A 的子集即可
  • 提取工具类型
    Typescript 类型的模式匹配是通过 extends 对类型参数做匹配,结果保存到通过 infer 声明的局部类型变量里,如果匹配就能从该局部变量里拿到提取出的类型。

    1
    2
    3
    type p = Promise<'test'>
    type getValueType<P> = P extends Promise<infer T> ? T : never
    type res_Type = getValueType<p> // type res_Type = "test"
    • 提取数组第一个 / 最后一个元素的类型

      1
      2
      3
      4
      5
      type arr = [number, string, boolean]
      type GetFirstType<T extends unknown[]> = T extends [infer F,...unknown[]] ? F : never
      type GetLastType<T extends unknown[]> = T extends [...unknown[],infer F] ? F : never
      type res_First = GetFirstType<arr> // type res_First = number
      type res_Last = GetLastType<arr> // type res_Last = boolean
    • 提取数组剩余元素类型

      1
      2
      type GetRestType<T extends unknown[]> = T extends [...infer F, unknown] ? F : never
      type res_Rest = GetRestType<arr> // type res_Rest = [number, string]
      • 替换字符串 replaceStr
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        // 匹配字符串开头结尾
        let str: `#${string}$` = '#str$'

        // 替换字符串
        type ReplaceStrType<
        Str extends string,
        From extends string,
        To extends string
        > = Str extends `${infer PreFix}${From}${infer EndFix}` ? `${PreFix}${To}${EndFix}` : never

        type str = ReplaceStrType<'my name is xxx', 'xxx', 'TypeScript'>
        // type str = "my name is TypeScript"
    • 去除字符串两边的空白 Trim

      1
      2
      3
      4
      // 递归删除空格 换行符等
      type TrimLeft<Str extends string> = Str extends `${' ' | '\n' | '\t'}${infer Rest}` ? TrimLeft<Rest> : Str
      type TrimRight<Str extends string> = Str extends `${infer Rest}${' ' | '\n' | '\t'}` ? TrimRight<Rest> : Str
      type res_Trim = TrimRight<TrimLeft<' 123 '>> // type res_Trim = "123"
      • 函数提取参数,返回值类型
        1
        2
        3
        4
        5
        6
        7
        8
        9
        // 提取参数
        type GetParams<Func extends Function> = Func extends (...args: infer Args) => unknown ? Args : never
        type res_Func = GetParams<(name: string, age: number) => boolean>
        // type res_Func = [name: string, age: number]

        // 提取返回值
        type GetReturns<Func extends Function> = Func extends (...args: any[]) => infer Return ? Return : never
        type res_Return = GetReturns<(name: string, age: number) => boolean>
        // type res_Return = boolean
    • 规定this指向 提取this类型

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      class Foo {
      name: string;

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

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

      const foo = new Foo()
      const obj = { a: 1 }
      foo.hello.apply(obj)
      // 类型“{ a: number; }”的参数不能赋给类型“Foo”的参数。
      // 类型“{ a: number; }”缺少类型“Foo”中的以下属性: name, hello

      // tsconfig.json
      {
      "compilerOptions": {
      "target": "ES2019", // 目标语言的版本
      "preserveConstEnums": true, // 保留 const 和 enum 声明
      "sourceMap": true, // 生成目标文件的sourceMap文件
      "strictBindCallApply": true
      }
      }

      // 提取this类型
      type GetThisType<Func extends Function> = Func extends (this: infer T, ...args: any[]) => unknown ? T : unknown
      type res_This = GetThisType<typeof foo.hello> // type res_This = Foo
    • 构造器的类型推断

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      // 定义class接口

      interface Point {
      x: number;
      y: number;
      }

      interface PointConstructor {
      new(x: number, y: number)
      }

      class Point implements Point {
      constructor(public x: number, public y: number) {
      }
      sum(): number {
      return this.x + this.y
      }
      }

      function myPoint(
      Constructor: PointConstructor,
      x: number,
      y: number
      ): Point {
      return new Constructor(x, y)
      }

      const p: Point = myPoint(Point, 1, 2)
      console.log(p.sum()); // 3

      // 构造器
      interface Point {
      x: number;
      y: number;
      }

      interface PointConstructor {
      new(x: number, y: number): Point
      }

      // 提取返回类型

      type GetInstanceType<Constructor extends new (...args: any[]) => unknown> = Constructor extends new (...args: any[]) => infer Instance ? Instance : any
      type res_Instance = GetInstanceType<PointConstructor> // type res_Instance = Point

      // 提取参数类型

      type GetParamsType<Constructor extends new (...args: any[]) => unknown> = Constructor extends new (...args: infer Rest) => any ? Rest : any
      type res_Params = GetParamsType<PointConstructor> // res_Params = [x: number, y: number]
    • 应用 提取Ref => PropsWithRef

      1
      2
      type GetRefProps<Props> = 'ref' extends keyof Props ? Props extends { ref?: infer Value } ? Value : never : never // 'ref' extends keyof Props 将Props遍历成联合类型 再查看里面是否右ref属性
      type res_Ref = GetRefProps<{ ref?: undefined, props: string }> // type res_Ref = Point
    • 翻转键值

      1
      2
      3
      4
      // 反转键名与键值
      type Reverse<T extends Record<string, unknown>> = T extends Record<infer K, infer V> ? Record<V & string, K> : never

      type res_Reverse = Reverse<{ "key": "value" }>; // { "value": "key" }

常用自定义工具类型

  1. IRes
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 给实际的响应数据预留坑位
    type IRes<Data> = {
    code: number,
    error?: string,
    data: Data
    }
    interface IUserRes {
    name: string;
    homepage: string;
    avatar: string;
    }
    interface IPaginationRes<TItem = unknown> {
    data: TItem[];
    page: number;
    totalCount: number;
    hasNextPage: boolean;
    }
    declare const res_List: IRes<IPaginationRes<IUserRes>>

    const { data, code } = res_List
    const { name, avatar, homepage } = data.data[0]

鸭子类型

  1. 结构化类型系统 只会比较结构是一致 如果结构一致 则认为输入的就是这个类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class Dog {
    say() { }
    }
    class Cat {
    say() { }
    }
    function animal(cat: Cat) {
    }
    animal(new Dog())
    // 无报错

    class Dog {
    say() { }
    }
    class Cat {
    say() { }
    eat() { }
    }
    function animal(cat: Cat) { }
    animal(new Dog())
    // 类型“Dog”的参数不能赋给类型“Cat”的参数。
    // 类型 "Dog" 中缺少属性 "eat",但类型 "Cat" 中需要该属性
  2. 标称类型系统 通过类型 / 逻辑来模拟标称类型系统
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Tag<T extends string>{
    protected __tag__: T
    }
    type Normial<T, U extends string> = T & Tag<U>

    export type CNY = Normial<number, 'CNY'>;
    export type USD = Normial<number, 'USD'>;

    const CNYCount = 100 as CNY;
    const USDCount = 100 as USD;

    function addCNY(source: CNY, input: CNY) {
    return (source + input) as CNY;
    }
    addCNY(CNYCount, CNYCount);
    // 报错了!
    addCNY(CNYCount, USDCount);
  3. 类型层级 Top Type & Bottom Type

函数类型的协变与逆变

在tsconfig.json开启strictFunctionTypes的情况下,TS会开启逆变。其作用在两个函数进行类型比较的情况下,对于参数的比较遵循类型系统的逆向,即: 函数类型的参数类型使用子类型逆变的方式确定是否成立,而返回值类型使用子类型协变的方式确定

不开strictFunctionTypes的情况下,TS默认双变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
interface Animal {
animal(): void
}

interface Dog extends Animal {
bark(): void
}

let haveAnimal = function haveAnimal(animal: Animal) {
animal.animal()
}
let haveDog = function haveDog(dog: Dog) {
dog.animal()
dog.bark()
}
haveAnimal = haveDog
// 不能将类型“(dog: Dog) => void”分配给类型“(animal: Animal) => void”

let dog: Dog = {
bark() {
},
animal() {
}
}

haveAnimal(dog)

TS工程化

  • 类型检查指令

    • ts-ignore
    • ts-except-error 二者均为忽略下一行的错误 但是ts-except-error更严谨
    • ts-check & ts-nocheck
    • ……
  • 类型声明
    通过额外的类型声明文件,在核心代码文件以外去提供对类型的进一步补全 。类型声明文件,即 .d.ts 结尾的文件,它会自动地被 TS 加载到环境中,实现对应部分代码的类型补全。

    • 无类型定义的npm包
      1
      2
      3
      4
      5
      6
      7
      // index.ts
      import foo from 'pkg';
      const res = foo.handler();
      // index.d.ts
      declare module 'pkg' {
      const handler: () => boolean;
      }
    • 非代码文件(png svg md module.scss等)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      // index.ts
      import raw from './note.md';

      const content = raw.replace('NOTE', `NOTE${new Date().getDay()}`);

      // declare.d.ts
      declare module '*.md' {
      const raw: string;
      export default raw;
      }
  • 命名空间

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    export namespace RealCurrency {
    export class WeChatPaySDK {}

    export class ALiPaySDK {}

    export class MeiTuanPaySDK {}

    export class CreditCardPaySDK {}
    }

    export namespace VirtualCurrency {
    export class QQCoinPaySDK {}

    export class BitCoinPaySDK {}

    export class ETHPaySDK {}
    }

装饰器


TS进阶
https://jing-jiu.github.io/jing-jiu/2022/10/20/notebooks/TS类型体操/(二)进阶/
作者
Jing-Jiu
发布于
2022年10月20日
许可协议