TS笔记(1-6)

220 阅读6分钟
装箱类型(Boxed Types) && 拆箱类型(Unboxed Types)

以Object、object举例,Object包括了其他非原始数据类型,包括Function、Array等,而拆箱类型object只表示对象,在任何情况都不应使用装箱类型

declare

只是快速生成一个符合类型,但没有实际值的变量,他不存在于runtime中,所以使用declare后直接使用变量会提示未声明

当你需要定义接口的返回类型,code可能为10000、10001、50000,status可能为"success"或"fail",可以使用联合类型字面量类型
interface Res {
    code: 10000 | 10001 | 50000,
    status: "success" | "fail"
}
当你编写类似CONSTANTS的常量文件时,使用enum枚举
enum CONSTANT {
  host = "localhost:i9",
  url = "sb",
  a = 1,
}
console.log(CONSTANT[1]); // 输出a
console.log(CONSTANT["sb"]; // undefined

enum和对象的区别在于,enum时双向映射的,即能从value映射到key,也能从key映射到value,但是注意,仅有值为number的可以双向映射。
enum编译后:

"use strict";
var Items;
(function (Items) {
  Items[(Items["Foo"] = 0)] = "Foo";
  Items[(Items["Bar"] = 1)] = "Bar";
  Items[(Items["Baz"] = 2)] = "Baz";
})(Items || (Items = {}));

obj[obj[k] = v] = k相当于 obj[k] = v and obj[v] = k

简单的声明一个函数类型
const fn = (name: string): number => {
  return 1;
};
函数重载,关联入参与出参,获取更佳的类型标注
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 * 599;
  }
}
const res1 = func(599); // number
const res2 = func(599, true); // string
const res3 = func(599, false); // number

ts的函数重载更像是伪重载,关键在于只有一个具体实现,其他的只是对入参出参进行标注。

class类

首先简单复习一下class

  • class本质仍是一个函数,类中定义的所有方法实际上都是定义在函数的显式原型上。
  • constructor方法默认返回实例对象,即this,当然你也可以创建新对象返回, constructor内定义的是实例的属性
  • 类的属性和方法,除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。
  • 定义实例的属性,除了显式用this指定,还可以将属性定义在最外层(最高层),这样也默认是定义在实例上的属性,即bar = 1 => this.bar = 1
  • 可以使用get或set定义方法对class属性进行读写的拦截
  • 静态方法static不会被实例继承,也就是说静态方法是定义在class构造函数身上,而不是定义在显式原型上,所以不会被继承。静态属性同理。
  • 私有方法和属性用#符号定义,如#foo = 1,私有的含义就是只能在当前类内访问,在外部是访问不到的,当然也不会被继承。注意与静态属性方法区别。
  • 静态块是一种只会在类生成的时候运行一次的代码块,之后生成实例就不会在运行了,使用static{ ... }编写,主要用于对class里的属性进行初始化时。
  • this指向,class内默认为严格模式,constructor内this指向实例,其他指向类,解构时注意this指向的变化。
class in Ts
  • set方法不能对返回值进行类型约束,可以理解为set方法只是关注过程
  • 修饰符public, private, protected, readonly, public为默认,类子类实例都能访问;private为私有,也就是js里的#符私有属性方法;protected只能在类和子类访问,不能在实例访问。
  • 在constructor内对入参进行访问性修饰符约束,此时入参属性会直接实例的属性,无需赋值(不需要this.xx = xx)
extends 继承

派生类 extends 基类,根据上述访问修饰符,我们得知派生类可以访问到基类里的public & protected, 除了访问外,我们也可以通过super关键字访问基类的方法,也可以使用override关键字对父类方法进行重写。

抽象类

使用abstract关键字进行声明,其内的方法、属性都用abstract符修饰,抽象类仅声明结构,类型,不做具体实现

abstract class AbsFoo {
  abstract absProp: string;
  abstract get absGetter(): string;
  abstract absMethod(name: string): string;
}
避免使用any的tips
  • 如果是类型不兼容报错导致你使用 any,考虑用类型断言替代,我们下面就会开始介绍类型断言的作用。
  • 如果是类型太复杂导致你不想全部声明而使用 any,考虑将这一处的类型去断言为你需要的最简类型。如你需要调用 foo.bar.baz(),就可以先将 foo 断言为一个具有 bar 方法的类型。
  • 如果你是想表达一个未知类型,更合理的方式是使用 unknown。
any 和 unknown

any 和 unknown 都是顶级类型, 区别在于unknown可以被再次赋值为任意类型,但其只能赋值给unknown或any类型, 对unknown类型进行属性访问,需要进行类型断言.

let unknownVar: unknown;
(unknownVar as { foo: () => {} }).foo();
never

never和unknown类型可以类比于undefined 和 null, never被认为是最底层的类型,是其他所有类型的子类型,只有never类型可以赋值never类型,使用场景如下:

  • 一个负责抛出错误的函数,never类型的函数执行后,其后的语句不会再被执行,也就是变成dead code
function throwErr(): never {
  throw new Error("never");
}
let a = 1;
function testError(): void {
  if (a > 0) {
    throwErr();
    a++;
  }
}
console.log(a);
  • 利用只有never类型能赋值给never类型,实现联合类型的类型检查
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
const arr = [];
arr.push("linbudu"); // 类型“string”的参数不能赋给类型“never”的参数。
类型断言

两种写法:包括bar as string<string>bar

let str: string | number;
str = "123";
console.log((str as string).length);
console.log((<string>str).length);
双重断言

当你使用类型断言时,原类型与断言类型差异过大,会提示错误

const str: string = "linbudu";

// 从 X 类型 到 Y 类型的断言可能是错误的,blabla
(str as { handler: () => {} }).handler();

此时,你需要先将类型断言成Unknown再断言成你想要的类型,如

const str: string = "linbudu";
(str as unknown as { handler: () => {} }).handler();
非空断言

使用!表示,实际上是类型断言的语法糖,如下面的例子

declare const foo: {
  func?: () => {
    prop?: number | null;
  };
};
foo.func!().prop!.toFixed();

实际上为

((   foo.func as () => {
      prop?: number;
    }
  )().prop as number
).toFixed();
  • 使用场景tip 当你定义了一个interface ABC,创建一个对象使用ABC进行类型约束,此时,当你不将ABC中的属性全部实现就会报错,你可以使用类型断言达到实现部分的效果。
interface ABC {
  foo: string;
  bar: {
    handler: () => void;
  };
}
const obj: ABC = {
  foo: "a",
};// obj中缺少属性 bar

const obj2: ABC = <ABC>{
  foo: "sad",
};