持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情
1. 类型断言
- 啥是类型断言? 把两种有重叠关系的数据类型进行相互转换的一种TS语法
- 格式
- A数据类型的变量
asB数据类型 <B>AA数据类型和B数据类型必须具有重叠关系
- A数据类型的变量
- 重叠关系场景:
- A、B是类,且存在继承关系。他们之间类型可以相互转换,但大多数情况下是把父类的实例对象断言成子类类型。
class Preson { ..... } class Student extends Preson { .... } let stu = new Student() as Preson; // 正确 let Pre = new Preson() as Student; // 正确 - A、B是类,但不存在继承关系。
类A中的所有
public修饰的属性和方法的集合与类B中的所有public修饰的属性和方法的集合一致或是其子集,则A、B两个类之间的类型可以相互转换。class Preson { public name: string; public sex: string;dxzxjhggggggg } class Student{ public name: string; public sex: string; public no: number; } let stu = new Student() as Preson; // 正确 let Pre = new Preson() as Student; // 正确 - A为类,B为
interface/type,且A类实现了B接口 可以相互断言成彼此的类型export interface Subject { a: string; b: number; } export class SubjectClass implements Subject { a: string; b: number; c: string constructor(a: string, b: number, c: string) { this.a = a; this.b = b; this.c = c; } } let sub = new SubjectClass('1', 2, 'c') as Subject // 正确 let interfaceSub: Subject = { a: '', b: 3 }; (interfaceSub as SubjectClass).c // 正确 - A为类,B为
interface/type,但A类没有实现B接口,同上述第2项export interface Subject { a: string; b: number; } export class SubjectClass { a: string; b: number; c: string constructor(a: string, b: number, c: string) { this.a = a; this.b = b; this.c = c; } } let sub = new SubjectClass('1', 2, 'c') as Subject // 正确 let interfaceSub: Subject = { a: '', b: 3 }; (interfaceSub as SubjectClass).c // 正确 - A的类型为联合类型
function getId(key: number | string) { key as number } - 任何数据类型都可以转换成
any或unknown类型,同时any或unknown类型也可以转换成其他任何类型
- A、B是类,且存在继承关系。他们之间类型可以相互转换,但大多数情况下是把父类的实例对象断言成子类类型。
2. TS类型守卫
-
new干了啥?newObj(constru, ...args) { // 先定义一个空对象 let obj = {}; // 链接到原型 obj.__proto__ = constru.prototype; // 执行构造函数,并把this指向前面定义的空对象 let result = constru.apply(obj, args) // 确保 new 出来的是个对象 return typeof result === 'object' ? result : obj } -
啥是类型守卫? 在块级作用域【if或条目运算符】内,通过对类型的判断,从而缩小TS对变量类型推断的范围。 就像校门空i守卫一样,要进门先要询问您是不是学生【if】,通过了守卫,才能进行学生具备的行为。
-
类型守卫发生阶段 TS条件语句中遇到下列条件关键字时,会在语句下的块级作用域内缩小变量类型的范围。类型守卫可以帮助我们在块级作用域中获得更加精确的变量类型
typeof、in、instanceof、==、===、!==、!=
3. TS多态
- 定义: 父类定义一个方法但不实现具体方法,由子类重写该方法,让子类以各自需要去实现不同的展示状态。
- 产生条件:
- 必须存在于继承关系
- 必须有方法重写
- 好处: 利于项目的扩展,从而局部满足了开闭原则(对修改关闭,对扩展开放)
- 多态的局限性:
无法直接调用子类独有的方法,必须结合
instanceof类型守卫来解决 - 具体例子:
顾客买菜,每种蔬菜的计价方式(
getTotalPrice)不同,但是顾客不需要了解各种蔬菜是怎么计价的(财大气粗的顾客),只知道蔬菜肯定是要付钱的,然后付钱就是了(遵纪守法的顾客)。
// 蔬菜类,这里最好应该用抽象类定义,下面会讲
class Vegetables {
public price: number;
constructor(price: number) {
this.price = price;
}
getTotalPrice(): number {
return 0;
}
}
class Radish extends Vegetables {
public num: number;
constructor(price: number, num: number) {
super(price);
this.num = num;
}
// 萝卜按个数卖
getTotalPrice(): number {
return this.price * this.num;
}
}
class Cauliflower extends Vegetables {
public weight: number;
constructor(price: number, weight: number) {
super(price);
this.weight = weight;
}
// 菜花按斤卖
getTotalPrice(): number {
return this.price * this.weight;
}
}
class Client {
pay(vegetables: Vegetables) {
// 更具传进来来的具体蔬菜,调用具体的计价方式
const price = vegetables.getTotalPrice();
// 下面的支付操作
// .....
}
}
const radish = new Radish(12, 1);
const flower = new Cauliflower(12, 3);
const client = new Client();
client.pay(radish);
client.pay(flower);
4. 抽象类
-
定义: 一个在任何位置都无法被实例化的类。在基类的基础上再进行抽象,当基类没有被实例化的需求时,可以采用抽象类。抽象类在基类的基础(可以有具体方法、实例属性、构造器)之上,增加了对属性的抽象,只需在属性名前面用
abstract修饰并定义具体属性的类型,在基础中起到与接口相似的功能。抽象属性可以在抽象类自身中被调用。 抽象类的功能:基类 + 接口 - 被实例化abstract class Animal { // 抽象属性 public abstract name: string public sex: string constructor(sex: string) { this.sex = sex } // 具体方法,在使用访问允许的情况下,可以被子类调用/重写 eat() { console.log('chi'); // 可以调用抽象方法 this.run(); } // 抽象方法,abstract修饰,只有方法签名 abstract run(): void } class Dog extends Animal { public name: string; constructor(name: string, sex: string) { super(sex); this.name = name } run(): void { console.log('四脚跑'); } } const dog = new Dog('zhang', 'nan') dog.eat(); // chi dog.run(); // '四脚跑' -
好处:
- 有统一的抽象方法,提高代码的可维护性:子类去继承抽象类时,就强制我们去实现抽象类中抽象属性。
- 避免去实现一个没有意义的类
-
具体例子,上述多态中的例子,蔬菜类是一个空乏的类,当我们去实例化蔬菜类似,发现这破玩意实例化了没啥用,且计价方式应该是每个子类都需要强制实现的,综上,我们应该采用抽象类。
abstract class Vegetables { public abstract price: number; public abstract getTotalPrice(): number; }
5. 自定义类型守卫
- 格式:
function 函数名(形参: 类型): 形参 is A类型(具体意义:当返回为true的时候,形参的类型即为A类型) { return true/false; } - 实际应用
let obj = { str: '123132', a: [] } Object.keys(obj).forEach(key => { /** * 直接使用会报这样的错误: * 元素隐式具有 "any" 类型,因为类型为 "string" 的表达式不能用于索引类型 "{ str: string; a: never[]; } "。 * 在类型 "{ str: string; a: never[]; }" 上找不到具有类型为 "string" 的参数的索引签名。 * 原因:在这里的key的类型为“string”,明显过于广泛。key的类型应该是每一个key名的联合类型,这里就可以使用自定义守卫 */ const value = obj[key]; }) // 采用自定义守卫,如下 // 当key在obj中时,返回true,这时候自定义守卫将key的类型转化为每一个key名的联合类型 // 这里的typeof是TS中操作符,与JS的不一样。能够反推定义的变量/常量的类型 // keyof, 用于获取某种类型的所有key,其返回类型是联合类型。 function isValidKey(key: string | number | symbol, object: object): key is keyof typeof object { return key in object; } // 不报错, 正常 Object.keys(obj).forEach(key => { if (isValidKey(key, obj)) { const value = obj[key]; } })
6. as const的运用
-
使用场景 当我们用
const去定义数组时,只是不能再次给该数组常量赋值,且能改变数组元素. 当我们希望数组常量的元素不能改变时,可以使用as constconst arr = [1, 2, 3] as const; // 当arr作为参数传入时,对应的方法形参类型前需要readonly修饰 function showArr(arr: readonly any[]) { log(arr) }