TypeScript学习

TypeScript

预计阅读时间: 13 分钟
"ts是什么

TypeScriptJavaScript 的超集,用于类型检查,在编译阶段提前发现错误,提升开发效率和代码安全性

主要是用于解决javaScript类型缺失,容易出错的缺点

"要不要使用ts?

这个问题见仁见智,但是我的观点是要用。

  • 使用ts可以提升开发效率,减少错误
  • 有AI辅助,使用ts增加的工作量可以忽略不计
  • 使用ts可以提升AI生成代码的准确性

综上,AI辅助下使用ts收益远远大于成本,必须要用上!

1.tsconfig 配置文件

"tsconfig配置
  • 格式: .json .js .ts 三种文件格式都可以识别
  • 作用
    1. 编译时选项,tsc 或者webpack中的ts-loader (实际在编译时使用的babel,这个文件不会影响)
    2. 代码编辑器的提示
tsconfig.json
1{
2  "compilerOptions": {},
3  "files":[],//用于指定编译的文件数组,文件较少时使用
4  "include":['src/**/*.ts'],//包含的文件
5  "exclude":[],//优先于include,排除的文件
6}

2. 环境搭建

shell
1# 全局安装typescript
2pnpm add typescript -g
3# 安装ts-node 和 @type/node 
4pnpm add  ts-node  @type/node
5# 初始化ts项目
6
7tsc --init
8# 运行ts文件
9ts-node src/index.ts
10# 运行ts文件
11ts-node src/index.ts

3. 类型推导/推断

"类型推导

声明变量的时候,直接赋值,会根据赋值的类型推导出标识符的类型注解,称为类型推导

  • let 声明的变量类型推导会是通用类型
  • const 声明的变量类型推导,推导出来的是字面量类型
"类型推断

ts会自动推断变量的类型,如果声明时没有赋值或声明类型,不管之后有没有赋值,都会被推断成 any 类型,从而编译报错

4. 预置数据类型

"预置数据类型

ts中有一些预先定义的类型,可以直接使用, 如 Iterable 可迭代类型

4.1 布尔类型

main.ts
1let isEnd: boolean = false;
2// isEnd只能是布尔类型,不能是其他类型

4.2 数字类型

"注意

numberNumber的区别

  • number ,ts中的number类型,js中不存在

  • Number是一个js中的类,是用来创建Number实例的,ts中也可以用

main.ts
1let num: number = 2;

4.3 字符串类型

main.ts
1let str: string = "1";

4.4 数组类型

  • 数组存放单一类型的值
main.ts
1let arr: string[] = ["1"];
2// 数组中只能放字符串
3
4let arr1: Array<number> = [1, 2, 3];
5// 写法2,很少用

4.5 void 类型

  • void类型的变量只能赋值为undefinednull以及没有返回值的函数
  • 通常使用在定义函数的数据类型的地方使用
main.ts
1function getData(a: string | number, b: string): void {
2  console.log("void");
3}
4//一个函数没有返回值时,返回值是void类型
5// 通常使用在定义函数的数据类型的地方使用
6type func:()=> void
7//当基于上下文类型推导中的函数,推导出来的函数返回值是viod时,如果返回一个值,并不会报错

4.6 对象类型

  • 直接使用类型注解适用于简单的场景
  • 使用接口定义复杂对象类型,方便维护
main.ts
1let obj:{ name:string } = {
2  name: '123',
3};
4// 直接对象,不加注解,利用类型推导,也可以

4.7 元组类型

main.ts
1let tuple: [string, number] = ["1", 2];
2// 元组类型注意注解的格式,同时必须对应赋值,注意写法,类型写在中括号中
3// 函数中使用元组最多,如react中使用useState

4.8 any 类型

  • 做任何操作都是合法的
main.ts
1let mar: any = 1;
2// 任意类型,当无法确定类型的时候,或者后面可能会发生改变的时候,使用该类型
3//不要滥用any类型,回到js
4//做任何操作都是合法的

4.9 undefined 类型

main.ts
1let und: undefined = undefined;
2// 只有一个值就是undefined

4.10 unknown 类型

  • 常用于类型强制转换
main.ts
1// 不确定的类型
2//和any 的类型的相似,区别是的,unknown类型上做任何操作都是非法的,需要先进行类型缩小,再进行对应操作
3
4// 类型断言
5TSomething as unknown as string

4.11 never 类型

  • 封装工具时,常用于类型断言
main.ts
1// 永远不能赋值的类型
2//用来先设置,或者函数中有死循环,或者函数中有报错,都会报错
3//一般自己不会主动定义,封装工具库时会使用
4handleMessage(msg:string|number){
5switch(typeof msg){
6  case 'String':
7     console.log(msg.length)
8  case 'Number':
9     console.log(msg)
10  default:
11     const check:never = msg
12  }
13} 
14//一旦需要扩展handleMessage方法时,添加了类型,而忘记添加实现,则会报错,增加代码的严谨性
15
16// 函数中使用never类型,表示函数中会抛出错误,或者死循环,或者永远不能执行到的地方
17function fail(msg: string): never {
18  throw new Error(msg);
19}

4.12 枚举类型

  • 常用于状态管理
main.ts
1//重要
2// type Direction = "left" | "Right" | "Top" | "Bottom"
3//LEFT=0  RIGHT=1  TOP=2  BOTTOM=3 这里是有默认值的,实际会对应到一个数值,并且可以赋值
4enum Direction {
5  LEFT, //LEFT=1,后面可以递增
6  RIGHT,
7  TOP,
8  BOTTOM,
9}
10
11function turnDirection(direction: Direction) {
12  switch (direction) {
13    case Direction.LEFT:
14      console.log("改变角色的方向向左");
15      break;
16    case Direction.RIGHT:
17      console.log("改变角色的方向向右");
18      break;
19    case Direction.TOP:
20      console.log("改变角色的方向向上");
21      break;
22    case Direction.BOTTOM:
23      console.log("改变角色的方向向下");
24      break;
25    default:
26      const foo: never = direction;
27      //   never的典型应用 当Direction 新增选项而turnDirection没有增加对应代码的时候,会报错
28      break;
29  }
30}
31
32turnDirection(Direction.LEFT);
33turnDirection(Direction.RIGHT);
34turnDirection(Direction.TOP);
35turnDirection(Direction.BOTTOM);

4.13 null 类型

main.ts
1// 只有一个值就是null
2let null: null = null;

4.14 函数参数的类型

main.ts
1function getData(num: number, str: string): string {
2  return num + str;
3}
4// 函数参数的类型 会做提示
5// 作为参数的匿名函数可以不做注解,会根据上下文推导
6// 函数的参数对象类型
7function getData(obj: { num: number; str?: string }): string {
8  console.log(obj.str);
9  return obj.num + "";
10}
11
12(val:number)=> string  // j箭头函数
13
14
15//可选参数的类型,是一个联合类型  原本的类型和 undefined
16
17//默认参数的类型也是联合类型,因为调用的时候也可以传入undefined
18
19//剩余参数的类型注解需要写成数组

4.15 联合类型

  • 并集,联合类型,满足其中一种类型即可
main.ts
1// 用竖线隔开,并集,联合类型,满足其中一种类型即可
2type MyType = number | string | boolean;

4.16 交叉类型

  • 多个类型都需要满足
main.ts
1//交叉类型 多个类型都需要满足
2interface IPerson {
3name: string
4}
5
6interface IStudent{
7 age:number
8}
9//既需要满足IPerson 也需要满足IStudent
10type Ikun = IPerson & IStudent

4.17 可选类型

main.ts
1// 参数即为可选
2function(message?:string){
3
4}

4.18 字符串字面量类型

main.ts
1let myName: "Tom" = "Tom";
2
3type EventNames = "click" | "scroll" | "mousemove";
4let event = "click";
5// 意义在于结合联合类型才有意义
6
7// 字面量推理
8type Request = {
9  // 方法一
10  url: string;
11  method: Method;
12};
13
14type Method = "GET" | "POST";
15
16const options: Request = {
17  url: "http://baidu.com",
18  method: "POST",
19} as const; //方法二
20
21// as const 会将options的属性变成只读属性,否则method是字面量类型,option里的method是string类型,
22//会报错
23
24function getData(url: string, method: Method) {}
25
26getData(options.url, options.method);

4.19 函数类型

main.ts
1//ts对传入的函数参数个数不校验!!!,能少不能多
2
3//函数本身也是一个标识符,自身也有类型
4
5//方案一、函数类型表达式(arg:string)=> void  没有返回值
6type addFn = (num:number,str:sting) => void
7const add:addFn = function (num,str){
8    return num+str
9}
10
11//方案二、函数调用签名 (从对象的角度看函数),当需要描述函数其他属性时,使用
12interface IFunc {
13name:string
14(num:number):void //调用签名
15new ():Person  //构造签名
16}
17
18const printNumber::IFunc = function(num:number) {
19  return num
20}

3. Class 类

类的特性: 封装、继承、多态

3.1 类的继承

"类的继承
  • 子类可以继承父类中的属性和方法,减少相同代码的重复编写
  • js/ts中是单继承,只能继承单一父类

3.2 类的多态:

"类的多态
  • 父类引用指向子类对象
  • 父类作为子类的实例的类型,这样可以提前确定好子类实例中拥有哪些方法
  • 中间层可以调用,中间层不再关注具体的实现,只需要调用对应的方法即可
main.ts
1//亦即父类作为子类的实例的类型,这样可以提前确定好子类实例中拥有哪些方法,
2//让中间层可以调用,中间层不再关注具体的实现,只需要调用对应的方法即可
3const square: Shape = new Square(10);  //见抽象类中的具体实现

3.3 类的封装

"类的封装
  • 紧凑的组织代码
  • 隐藏类的内部实现,只暴露接口给外部使用
  • 提高类的可维护性
  • 提高类的可扩展性

3.4 类成员修饰符

  • 成员修饰符,控制属性的访问权限

    • public 可公开访问 【default】
    • private 私有属性,只能在定义它的类内部访问,子类和实例都无法访问
    • protected 保护属性,可以在定义它的类内部和子类内部访问,但实例无法访问
    • readonly 只能在声明时或构造函数里被初始化,其他地方不能修改
    • static 静态属性,类可以访问,实例不能访问
  • 当构造函数修饰为 protected 时,该类只允许被继承

  • 当构造函数修饰为 private 时,该类不允许被继承或者实例化 【单例模式】

3.5 抽象类

"抽象类

抽象类的作用,多用于多态的实现,分层封装

  • 抽象类不能被实例化,只能被继承
  • 抽象类中的抽象方法必须被子类实现
  • 抽象类中的抽象方法可以没有具体实现,也可以有具体实现
main.ts
1// 抽象类
2abstract class Shape {
3  abstract getAera(): number;
4}
5
6class Circle extends Shape {
7  private r: number;
8  constructor(radius: number) {
9    super();
10    this.r = radius;
11  }
12  getAera() {
13    return Math.PI * this.r ** 2;
14  }
15}
16
17class Square extends Shape {
18  private a: number;
19  constructor(a: number) {
20    super();
21    this.a = a;
22  }
23  getAera() {
24    return this.a ** 2;
25  }
26}
27
28function makeAera(a: Shape) {
29  return a.getAera();
30}
31
32const circle = new Circle(10);
33const square: Shape = new Square(10);
34console.log(makeAera(circle));
35console.log(makeAera(square));

3.6 类的访问器 getter /setter

"类的访问器

访问器是一种特殊的方法,用来读取或设置某个属性的值

  • 访问器不会被编译到 js 中,只会在编译阶段进行类型检查
  • 访问器不必须有 get 和 set 方法,可以只有 get 方法,也可以只有 set 方法,一般用于拦截私有属性的操作
  • 只实现get 方法,那么该属性为 readonly
  • 访问器的参数不能有修饰符,也不能有默认值
  • 访问器可以重载
main.ts
1class Person {
2  private _name: string;
3  constructor(name: string) {
4    this._name = name;
5  }
6  get name() {
7    return this._name;
8  }
9  set name(value: string) {
10    this._name = value;
11  }
12}
13
14const p = new Person('Max');
15p.name = 'Tom';
16console.log(p.name);

3.7 参数属性(TS语法糖)

"参数属性

在constructor方法中,参数前面加上修饰符,即可简化属性的编写

main.ts
1class Person {
2
3  constructor(public name: string, private age: number,protected height: number) {
4
5  }
6}
7
8const p1 = new Person('Max', 30);
9console.log(p1.name);

3.8 类的类型特性

"类的类型特性
  • 类可以创建实例
  • 类可以作为实例的类型
  • 类可以作为有构造签名的函数-工厂函数中使用
main.ts
1// 定义一个基类
2class Animal {
3    name: string;
4
5    constructor(name: string) {
6        this.name = name;
7    }
8
9    speak(): void {
10        console.log(`${this.name} makes a noise.`);
11    }
12}
13
14// 定义一个子类
15class Dog extends Animal {
16    constructor(name: string) {
17        super(name);
18    }
19
20    speak(): void {
21        console.log(`${this.name} barks.`);
22    }
23}
24
25// 定义一个工厂函数
26function createAnimal<T extends Animal>(AnimalClass: new (name: string) => T, name: string): T {
27    return new AnimalClass(name);
28}
29
30// 使用工厂函数创建 Animal 实例
31const animal = createAnimal(Animal, "Generic Animal");
32animal.speak(); // 输出: Generic Animal makes a noise.
33
34// 使用工厂函数创建 Dog 实例
35const dog = createAnimal(Dog, "Buddy");
36dog.speak(); // 输出: Buddy barks.

4. interface 接口

"接口

主要用于声明对象类型,接口中的属性和方法都是抽象的,不能有具体实现

main.ts
1interface Itable {
2  head: string;
3  body: string;
4  foot?: string; //可选属性
5  content?: {
6    name: string;
7    age: number;
8  };
9
10}
11
12// 键值对的定义方式 内置的Record 
13interface IndexItem {
14  [index: string]: string;
15}
16
17// 函数类型的接口
18interface IAdd {
19  (a: number, b: number): number;
20}

4.1 接口的继承

  • 接口可以继承多个接口
main.ts
1interface ISwim {
2  swimming: () = void;
3}
4
5interface IFly {
6  flying: () = void;
7}
8
9interface IAction extends ISwim, IFly {}
10
11const action: IAction = {
12  swimming() {},
13  flying() {},
14};

4.2 接口的合并

main.ts
1interface ISwim {
2  swimming: () = void;
3}
4
5interface IFly {
6  flying: () = void;
7}
8
9type MyType1 = ISwim | IFly;
10type MyType2 = ISwim & IFly;
11
12const obj1: MyType1 = {
13  flying() {},
14};
15
16const obj2: MyType2 = {
17  swimming() {},
18  flying() {},
19};

4.3 接口的实现

main.ts
1interface ISwim {
2  swimming: () = void;
3}
4
5interface IEat {
6  eating: () = void;
7}
8
9// 类实现接口
10class Animal {}
11
12// 继承: js只能实现单继承
13// 实现: 实现接口, 类可以实现多个接口
14// 类实现 可以比接口定义的属性多
15class Fish extends Animal implements ISwim, IEat {
16  swimming() {
17    console.log("Fish Swmming");
18  }
19
20  eating() {
21    console.log("Fish Eating");
22  }
23}
24
25class Person<T implements ISwim<T {
26  // 可传泛型
27  swimming() {
28    console.log("Person Swimming");
29  }
30}
31
32// 编写一些公共的API: 面向接口编程
33function swimAction(swimable: ISwim) {
34  swimable.swimming();
35}
36
37// 1.所有实现了接口的类对应的对象, 都是可以传入
38swimAction(new Fish());
39swimAction(new Person());
40
41swimAction({ swimming: function () {} });

4.4 interface 和 type 的区别

特性继承接口定义多个相同实现接口定义函数类型声明基本类型
type
interface✅ 内容会合并
"interface 和 type 的区别

建议基本类型使用type,对象类型使用interface

5. 泛型

"泛型

泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,使用一个变量代替,而在使用的时候再指定类型的一种特性

泛型的思想可以理解为把类型抽象成一个变量,在定义函数或者类的时候,传入类型的形参,使用的时候传入或根据参数推导具体的类型

泛型

泛型的类型限制是通过接口的继承实现的

main.ts
1// 首先定义一个具有长度的类型 接口
2interface ILength {
3  length: number;
4}
5
6// 函数传入的类型形参是继承自ILength的类型,则必需实现了length属性,否则会报错
7// 泛型约束
8function getLength<T extends ILength(arg: T): number {
9  return arg.length;
10  // 而且传入的参数类型必须是ILength的子类 ,所以肯定具有length属性
11}
12
13// 所以可见,使用泛型具有通用的约束参数类型的作用
14
15// 传入的参数类型也可以是多个或者有默认值的
16
17function getLength2<T extends ILength, K = string>(arg1: T, arg2: K): number {
18  return arg1.length + arg2.length;
19}
20
21// 调用函数可以不直接写具体的传入类型,函数能够自动推导出类型
22getLength2("abc", "def");

5.1 泛型约束

1. 简单约束
main.ts
1interface ILength {
2  length: number;
3}
4
5// 函数传入的类型形参是继承自ILength的类型,则必需实现了length属性,否则会报错
6function getLength<T extends ILength(arg: T): number {
7  return arg.length;
8  // 而且传入的参数类型必须是ILength的子类 ,所以肯定具有length属性
9}
2. keyof
  • 用来约束类型必须是对象中的一个key
main.ts
1interface IPerson {
2  name:string
3  age: number
4  height:string
5}
6
7type PersonKeys = keyof IPerson // 'name' | 'age' | 'height'
8//约束K必须是IPerson当中的一个key
9function getLength<T extends IPerson,K extends keyof IPerson >(arg: T,arg2:K): number {
10  return arg.age;
11}
3. 映射类型
  • 使用对象索引签名实现对一个接口的复制,并且对其修改
main.ts
1interface IPerson {
2  readonly name: string;
3  age: number;
4  height: string;
5}
6//可以使用+ - 来增加或者去除原来类型中的修饰符
7// 使用的索引签名 和 类型提取实现类型复制
8
9//需用type
10type MapIPerson<T> = {
11 - readonly [prop in keyof T]-?: T[prop]   
12}
13
14type NewPerson = MapIPerson<IPerson>
15//即可复制IPerson的属性,需用type

6. ts知识扩展

6.1 类型断言

"类型断言
  • as 定义为某一个类型,如果需要强制转换,需要先转换成unknown 再转换成所需类型
  • ! 非空断言 值不为空
main.ts
1//1. as关键词
2// 用来告诉ts变量的实际类型
3//类型断言只能把类型断言成更加(从不太具体的类型断言成)具体,或者(从比较的具体的类型断言成)更加不太具体的类型
4class Person{}
5
6class Student extends Person{
7    studying() {}
8}
9
10function sayHello(p:Person) {
11    (p as Student).studying()
12    // 把p断言为子类,就可以调用子类的方法
13}
14
15let stu=new Student();
16sayHello(stu)
17
18
19//2. 非空断言 ! 
20function sayHello(name?: string) {
21    //非空断言,保证非空,调用方法
22  console.log( name!.length);
23}
24
25// 3.扩展
26// 1. 可选链 ?. 用来判断是否有值
27// 2. !! 转换成布尔值
28// 3. ?? 用来判断是否有值,有值就返回,没有值就返回后面的值,比三目运算符更加方便

6.2 类型别名

main.ts
1type myType = number | string | boolean;
2
3// 使用type来定义类型别名
4type myObjType = {
5  name: string;
6  age: number;
7  height?: number;
8};
9// 对象别名也可以是对象

6.3 类型缩小

"类型缩小

缩小类型的范围,从而可以使用特定类型的方法

  • 真值缩小
  • typeof 类型保护
  • instanceof 类型保护
  • in 操作符类型保护
  • 平等缩小 === /!==
main.ts
1//亦即在(联合类型)变量使用的时候,多一层判断,确认最终类型
2
3type IDtype = string |number
4
5function getData(id:IDtype){
6    if(typeof id === 'string'){
7        //此时id就是string类型
8        console.log(id.length)
9     }
10}
11
12// in的判断方式
13
14type Dog = {
15    bark(){}
16}
17type Cat = {
18    Miao(){ }
19}
20function sound(p:Cat|Dog){
21    // 通过in来判断
22    if('bark' in p){
23        p.bark()
24    }else{
25        p.Miao()
26    }
27}
28
29const pet:Dog= {
30    bark(){
31        console.log('汪汪汪')
32    }
33}
34sound(pet)

6.4 函数的重载

"函数的重载
  • 定义相同名称,参数不同的几个函数签名,只有一个实现函数,ts会根据参数的类型 来判断调用哪个函数
main.ts
1function add(a: number, b: number): number;
2function add(a: string, b: string): string;
3function add(a: any, b: any): any {
4  return a + b;
5}
6
7console.log(add(10, 20));
8console.log(add("10", "20"));
9//实际
10
11//能通过联合类型实现的优先使用联合类型,不能实现的才使用函数重载,比较麻烦

6.5 鸭子类型

"鸭子类型

一个对象看起来像鸭子,叫声像鸭子,行为像鸭子,那么可以认为它是一个鸭子

ts中的类型检测使用的鸭子类型

鸭子类型是一种动态类型的概念,它是指在运行时,一个对象的有效语义是由它当前方法和属性的集合决定的,而不是由它所继承的特定类或接口的集合决定的。

这意味着,只要一个对象具有与所需方法和属性相同的方法和属性,它就可以被视为是所需的类型。这种类型检查方式通常用于动态语言中,如JavaScriptPython

6.6 修饰符

"修饰符

对象和接口中的属性都可以使用的修饰符

  • ?. 可选
  • Readonly 只读

6.7 对象接口索引签名

相当于一个动态属性

main.ts
1type Person = {
2  [index: number]: string  //允许通过索引获取值
3  lenght: number
4}
5
6interface Person  {
7  [index: number]: string; //允许通过索引获取值
8  lenght: number;
9  [key:string]:string|number  //[]中的类型只能是string 或者number ,不能是其他,联合也不行
10};
11//1. 可以使用2个索引签名的时候, [key:string]返回的类型必要是[index: number]返回的类型的父类型
12//因为number类型最终会转换成string
13
14//2. 其他属性( lenght)也必须符合[key:string]返回的类型
15
16//其实就是一条:其他属性( 包含[index: number])也必须符合[key:string]返回的类型的子类型

6.8 字面量赋值

"字面量赋值

严格对象字面量赋值 在使用字面量对接口或者类型赋值时,直接赋值会严格校验字面量的属性必须和接口保持一致,如下例子

main.ts
1interface IPerson {
2  name: string;
3  age: number;
4  height: number;
5}
6
7 const info = {
8   name: "gaoq",
9   age: 18,
10   height: 1.80,
11   address: "深圳市"
12 }
13
14
15 const p2 : IPerson = {
16   name: "gaoq",
17   age: 18,
18   height: 1.80,
19   address: "深圳市"
20 }
21 //这样会报错
22 
23
24 const p: IPerson = info
25 // freshness擦除 类型检测时
26//这样不会报错
27// console.log(info)
28// console.log(p)
29
30function printInfo(person: IPerson) {
31  console.log(person);
32}
33
34// 代码会报错
35 printInfo({
36   name: "why",
37   age: 18,
38   height: 1.88,
39   address: "广州市"
40 })
41
42
43const info = {
44  name: "why",
45  age: 18,
46  height: 1.88,
47  address: "广州市",
48};
49// 使用对象的引用,会在类型检测的时候有擦除,所以不会报错
50printInfo(info);

7 ts的知识扩展

7.1. 模块化

"模块化
  • 支持各种模块化 最推荐的模块化方式是esm
  • ts规范声明任何没有export的ts文件都会认为是一个脚本,而非一个模块
  • 在一个脚本文件中,变量和类型会被声明在共享的全局作用域,将多个输入文件合并成一个输出文件
  • 如果希望它被作为一个模块处理,需要添加export {}
main.ts
1export {}
2这会把文件当成单独的模块处理,有独立的作用域,不会污染作用域
3- 类型的导入导出
4//方法一
5import type { IPerson } from './type'
6//方法二
7import { type  IPerson } from './type'
8
9
10//type.ts
11export interface IPerson {
12 name:string
13}

使用type关键词的好处,使用babel、esbuild等编译时,方便识别安全移除,提升效率

7.2. 类型查找-类型声明

"类型声明

.d.ts文件,主要用于类型声明,不写代码逻辑,主要类型

  • 内部类型声明 安装typescript后,会自动生成
  • 外部类型声明 自定义代码类型声明
  • 第三方库类型声明
global.d.ts
1//声明变量
2declare const age:number
3
4//声明模块
5declare module 'lodash '{
6//模块内部需要导出
7  export function join(...args:any[]):any
8}
9
10//声明静态资源
11declare  module '*.jpg'

8. ts内置工具

详见:TypeScript内置工具详谈

main.ts
1interface IFunc {
2  name: string;
3  (this: { name: string }, num: number): void;
4}
5
6const printNumber: IFunc = function (this, num) {
7  console.log(num);
8};
9
10
11printNumber.name = "print";
12
13//获取函数的类型
14type printType = typeof printNumber;
15//ThisParameterType获取这个类型的this
16type This = ThisParameterType<printType>;
17
18//提取除了this以外的类型
19type pureType = OmitThisParameter<printType>;
20
21//绑定上下文的方法
22ThisType<printType>;

9.TS小技巧

9.1. 根据值推导共享与私有类型

"根据值推导共享与私有类型

作为一个缩小类型范围的手段

不同类型的对象共享相同的基础属性,但能根据自己的类型拥有私有属性

交叉类型是将多个类型合并为一个类型。这意味着你可以将多个类型的属性合并到一个类型中。

例如:

main.ts
1type CommonProps = {
2  commonProp: boolean;
3};
4type TypeA = {
5  type: 'A';
6  aProp: string;
7};
8type TypeB = {
9  type: 'B';
10  bProp: number;
11};
12type MyObject = CommonProps & (TypeA | TypeB);
13const obj1: MyObject = { type: 'A', aProp: 'Hello', commonProp: true };
14const obj2: MyObject = { type: 'B', bProp: 123, commonProp: false };

在这个例子中,MyObject 包含了 CommonProps 中的属性以及 TypeATypeB 中的属性。这样,无论对象是 TypeA 还是 TypeB,它都会有一个 commonProp 属性。