关于javascript:TypeScript学习笔记TS类型高级用法及实战优缺点

24次阅读

共计 6261 个字符,预计需要花费 16 分钟才能阅读完成。

最近这两年,有很多人都在探讨 Typescript,无论是社区还是各种文章都能看进去,整体来说侧面的信息是大于负面的,这篇文章就来整顿一下我所理解的 Typescript。

Typescript 类型

Typescript 有哪些类型

1、Typescript 根本类型,也就是能够被间接应用的繁多类型。

  • 数字
  • 字符串
  • 布尔类型
  • null
  • undefined
  • any
  • unknown
  • void
  • object
  • 枚举
  • never

2、复合类型,蕴含多个繁多类型的类型。

  • 数组类型
  • 元组类型
  • 字面量类型
  • 接口类型

3、如果一个类型不能满足要求怎么办?

  • 可空类型,默认任何类型都能够被赋值成 null 或 undefined。
  • 联结类型,不确定类型是哪个,但能提供几种抉择,如:type1 | type2。
  • 穿插类型,必须满足多个类型的组合,如:type1 & type2

类型都在哪里应用

在变量中应用
在变量中应用时,间接在变量前面加上类型即可。

let a: number;
let b: string;
let c: null;
let d: undefined;
let e: boolean;
let obj: Ixxx = {
  a: 1,
  b: 2,
};
let fun: Iyyy = () => {};

在接口中应用
在接口中应用也比较简单,能够了解为组合多个繁多类型。

interface IData {
  name: string;
  age: number;
  func: (s: string) => void;
}

在函数中应用
在函数中应用类型时,次要用于解决函数参数、函数返回值。

// 函数参数
function a(all: string) {}
// 函数返回值
function a(a: string): string {}
// 可选参数
function a(a: number, b?: number) {}

Typescript 高级用法

Typescript 中的根本用法非常简单,有 js 根底的同学很快就能上手,接下来咱们剖析一下 Typescript 中更高级的用法,以实现更精细的类型查看。

类中的高级用法

在类中的高级用法次要有以下几点:

  • 继承
  • 存储器 get set
  • readonly 修饰符
  • df 私有,公有,受爱护的修饰符
  • 抽象类 abstract

继承和存储器和 ES6 里的性能是统一的,这里就不多说了,次要说一下类的修饰符和抽象类。

类中的修饰符是体现面向对象封装性的次要伎俩,类中的属性和办法在被不同修饰符润饰之后,就有了不同权限的划分,例如:

  • public 示意在以后类、子类、实例中都能拜访。
  • protected 示意只能在以后类、子类中拜访。
  • private 示意只能在以后类拜访。
class Animal {
  // 私有,公有,受爱护的修饰符
  protected AnimalName: string;
  readonly age: number;
  static type: string;
  private _age: number;
  // 属性存储器
  get age(): number {return this._age;}
  set age(age: number) {this._age = age;}
  run() {console.log("run", this.AnimalName, this.age);
  }
  constructor(theName: string) {this.AnimalName = theName;}
}
Animal.type = "2"; // 动态属性
const dog = new Animal("dog");
dog.age = 2; // 给 readonly 属性赋值会报错
dog.AnimalName; // 实例中拜访 protected 报错
dog.run; // 失常

在类中的继承也非常简略,和 ES6 的语法是一样的。

class Cat extends Animal {dump() {console.log(this.AnimalName);
  }
}
let cat = new Cat("catname");

cat.AnimalName; // 受爱护的对象,报错
cat.run; // 失常
cat.age = 2; // 失常

在面向对象中,有一个比拟重要的概念就是抽象类,抽象类用于类的形象,能够定义一些类的公共属性、公共办法,让继承的子类去实现,也能够本人实现。

抽象类有以下两个特点:

  • 抽象类不能间接实例化
  • 抽象类中的形象属性和办法,必须被子类实现

tip 经典问题:抽象类的接口的区别:

  • 抽象类要被子类继承,接口要被类实现。
  • 在 ts 中应用 extends 去继承一个抽象类。
  • 在 ts 中应用 implements 去实现一个接口。
  • 接口只能做办法申明,抽象类中能够作办法申明,也能够做办法实现。
  • 抽象类是有法则的,抽离的是一个类别的公共局部,而接口只是对雷同属性和办法的形象,属性和办法能够无任何关联。

抽象类的用法如下:

abstract class Animal {abstract makeSound(): void;
  // 间接定义方法实例
  move(): void {console.log("roaming the earch...");
  }
}
class Cat extends Animal {makeSound() {} // 必须实现的形象办法
  move() {console.log('move');
  }
}
new Cat3();

接口中的高级用法

接口中的高级用法次要有以下几点:

  • 继承
  • 可选属性
  • 只读属性
  • 索引类型:字符串和数字
  • 函数类型接口
  • 给类增加类型,构造函数类型

接口中除了能够定义惯例属性之外,还能够定义可选属性、索引类型等。

interface Ia {
  a: string;
  b?: string; // 可选属性
  readonly c: number; // 只读属性
  [key: number]: string; // 索引类型
}
// 接口继承
interface Ib extends Ia {age: number;}
let test1: Ia = {
  a: "",
  c: 2,
  age: 1,
};
test1.c = 2; // 报错,只读属性
const item0 = test1[0]; // 索引类型

接口中同时也反对定义函数类型、构造函数类型。

// 接口定义函数类型
interface SearchFunc {(source: string, subString: string): boolean;
}
let mySearch: SearchFunc = function (x: string, y: string) {return false;};
// 接口中编写类的构造函数类型查看
interface IClass {new (hour: number, minute: number);
}
let test2: IClass = class {constructor(x: number, y: number) {}};

函数中的高级用法

函数重载
函数重载指的是一个函数能够依据不同的入参匹配对应的类型。

例如:案例中的 doSomeThing 在传一个参数的时候被提醒为 number 类型,传两个参数的话,第一个参数就必须是 string 类型。

// 函数重载
function doSomeThing(x: string, y: number): string;
function doSomeThing(x: number): string;
function doSomeThing(x): any {}

let result = doSomeThing(0);
let result1 = doSomeThing("", 2);

This 类型
咱们都晓得,Javascript 中的 this 只有在运行的时候,才可能判断,所以对于 Typescript 来说是很难做动态判断的,对此 Typescript 给咱们提供了手动绑定 this 类型,让咱们可能在明确 this 的状况下,给到动态的类型提醒。

其实在 Javascript 中的 this,就只有这五种状况:

  • 对象调用,指向调用的对象
  • 全局函数调用,指向 window 对象
  • call apply 调用,指向绑定的对象
  • dom.addEventListener 调用,指向 dom
  • 箭头函数中的 this,指向绑定时的上下文
// 全局函数调用 - window
function doSomeThing() {return this;}
const result2 = doSomeThing();

// 对象调用 - 对象
interface IObj {
  age: number;
  // 手动指定 this 类型
  doSomeThing(this: IObj): IObj;
  doSomeThing2(): Function;}

const obj: IObj = {
  age: 12,
  doSomeThing: function () {return this;},
  doSomeThing2: () => {console.log(this);
  },
};
const result3 = obj.doSomeThing();
let globalDoSomeThing = obj.doSomeThing;
globalDoSomeThing(); // 这样会报错,因为咱们只容许在对象中调用

// call apply 绑定对应的对象
function fn() {console.log(this);
}
fn.bind(document)();

// dom.addEventListener
document.body.addEventListener("click", function () {console.log(this); // body
});

泛型

泛型示意的是一个类型在定义时并不确定,须要在调用的时候能力确定的类型,次要蕴含以下几个知识点:

  • 泛型函数
  • 泛型类
  • 泛型束缚 T extends XXX

咱们试想一下,如果一个函数,把传入的参数间接输入,咱们怎么去给它编写类型?传入的参数能够是任何类型,难道咱们须要把每个类型都写一遍?

  • 应用函数重载,得把每个类型都写一遍,不适宜。
  • 泛型,用一个类型占位 T 去代替,在应用时指定对应的类型即可。
// 应用泛型
function doSomeThing<T>(param: T): T {return param;}

let y = doSomeThing(1);

// 泛型类
class MyClass<T> {log(msg: T) {return msg;}
}

let my = new MyClass<string>();
my.log("");

// 泛型束缚,能够规定最终执行时,只能是哪些类型
function d2<T extends string | number>(param: T): T {return param;}
let z = d2(true);

其实泛型原本很简略,但许多初学 Typescript 的同学感觉泛型很难,其实是因为泛型能够联合索引查问符 keyof、索引拜访符 T[k] 等写出难以浏览的代码,咱们来看一下。

// 以下四种办法,表白的含意是统一的,都是把对象中的某一个属性的 value 取出来,组成一个数组
function showKey1<K extends keyof T, T>(items: K[], obj: T): T[K][] {return items.map((item) => obj[item]);
}

function showKey2<K extends keyof T, T>(items: K[], obj: T): Array<T[K]> {return items.map((item) => obj[item]);
}

function showKey3<K extends keyof T, T>(items: K[],
  obj: {[K in keyof T]: any }
): T[K][] {return items.map((item) => obj[item]);
}

function showKey4<K extends keyof T, T>(items: K[],
  obj: {[K in keyof T]: any }
): Array<T[K]> {return items.map((item) => obj[item]);
}

let obj22 = showKey4<"age", {name: string; age: number}>(["age"], {
  name: "yhl",
  age: 12,
});

高级类型

Typescript 中的高级类型包含:穿插类型、联结类型、字面量类型、索引类型、映射类型等,这里咱们次要讨论一下

  • 联结类型
  • 映射类型

联结类型
联结类型是指一个对象可能是多个类型中的一个,如:let a :number | string 示意 a 要么是 number 类型,要么是 string 类型。

那么问题来了,咱们怎么去确定运行时到底是什么类型?

答案是类型爱护。类型爱护是针对于联结类型,让咱们可能通过逻辑判断,确定最终的类型,是来自联结类型中的哪个类型。

判断联结类型的办法很多:

  • typeof
  • instanceof
  • in
  • 字面量爱护,===、!===、==、!=
  • 自定义类型爱护,通过判断是否有某个属性等
// 自定义类型爱护
function isFish(pet: Fish | Bird): pet is Fish {return (<Fish>pet).swim !== undefined;
}
if (isFish(pet)) {pet.swim();
} else {pet.fly();
}

映射类型
映射类型示意能够对某一个类型进行操作,产生出另一个合乎咱们要求的类型:

  • ReadOnly,将 T 中的类型都变为只读。
  • Partial,将 T 中的类型都变为可选。
  • Exclude,从 T 中剔除能够赋值给 U 的类型。
  • Extract,提取 T 中能够赋值给 U 的类型。
  • NonNullable,从 T 中剔除 null 和 undefined。
  • ReturnType,获取函数返回值类型。
  • InstanceType,获取构造函数类型的实例类型。

咱们也能够编写自定义的映射类型。

// 定义 toPromise 映射
type ToPromise<T> = {[K in keyof T]: Promise<T[K]> };
type NumberList = [number, number];
type PromiseCoordinate = ToPromise<NumberList>;
// [Promise<number>, Promise<number>]

Typescript 总结

Typescript 长处

1、动态类型查看,提前发现问题。

2、类型即文档,便于了解,合作。

3、类型推导,主动补全,晋升开发效率。

4、出错时,能够大概率排除类型问题,缩短 bug 解决工夫。

实战中的长处:

1、发现 es 标准中弃用的办法,如:Date.toGMTString。

2、防止了一些不敌对的开发代码,如:动静给 obj 增加属性。

3、vue 应用变量,如果没有在 data 定义,会间接抛出问题。

Typescript 毛病

1、短期减少开发成本。

2、局部库还没有写 types 文件。

3、不是齐全的超集。

实战中的问题:

1、还有一些坑不好解决,axios 编写了拦截器之后,typescript 反映不到 response 中去。

Typescript 学习

对于 Typescript 的入门学习,我本人学习时看的是阿里大佬写的 Typescript 学习指南,一共分为 16 个类目去解说 Typescript,包含文章中讲到的内容也在这份指南中,对于想 ts 具体学习的或想查漏补缺的小伙伴都很适宜。

篇幅起因就不列举 Typescript 学习指南文档内容,完整版的【间接点击获取】,一起进入 TS 的世界。

结尾

如果你工作在一个大中型我的项目下面,typescript 对你应该是利大于弊。能够学!还能从另外一个不便理解动态类型语言是怎么玩的,看到他人的 Java 代码竟然能有看得懂的局部了。当然要学会依据本人的需要和我的项目的规模正当选用工具,如果你的利用就是一个简略的展现页面,加几个 UI 状态扭转,就没有必要应用。

正文完
 0