Typescript快速上手指南

465 阅读11分钟

@[toc]

TS的类型

首先ts支持JavaScript的所有基本类型,除此之外还多出了一些类型。

1.布尔类型

这是js与ts都具备的基本类型。

let flag:boolean=false;

2.数字类型

和JavaScript的数字类型相似,typescript中的数组类型都是浮点型,它们的类型都是number。我们我们清楚在JavaScript中的数字类型的范围为-2^53 ------ 2^53(包含边界)

3.字符串类型

这与JavaScript的基本类型string也没有什么区别。我们也可以使用模板字符串来进行定义多行文本和内嵌表达式。

let name: string = `Gene`;
let age: number = 37;
let sentence: string = `Hello, my name is ${ name }.
I'll be ${ age + 1 } years old next month.`;

4.数组类型

ts中也会有数组的概念,不过不一样的是在我们JavaScript中数组中的元素是不统一的,但是在ts中我们数组中的元素需要我们事前定义好。并且他有两种定义方式。

第一种:在元素类型后面接上[],这就表示我们这个数组中所有元素的类型是这个类型。

	let arr:number[]=[1,2,3,4]

第二种:使用数组泛型Array<元素类型>

let arr3:Array<string>=['1','2','4']

5.元组类型

元素实际上就是我们可以已知数量,并且元素类型可以不同的数组。

比如我们可以定义一个元素为number和string长度为2的数组就可以这样定义:

let tupo1:[string,number];
tupo1=['12',34]

6.枚举类型

这是一种在JavaScript中没有的类型,但是在其他类型中,例如Java、C#中都会有这种类型,其实枚举,顾名思义,就是利用一些形象的东西来定义抽象的东西,例如我们可以为一些数值赋一些形象的名字。

enum Color {red=2,yellow,blue};
let c:Color=Color.red;
console.log(c)

默认情况下,从0开始为元素编号,后面元素依次累加1即可,同时我们也可以自己指定数组,就像上面的例子一样

枚举类型哈有一个好处我们可以通过枚举的值得到他的名字,我们我们有时候知道数字但是不知道它对应的枚举名。

enum Color {red=2,yellow,blue};
let ename:string=Color[2]
console.log(ename)//显示red

7.any类型

当我们无法确定一个变量的类型时或者无需确定类型时,我们就可以将我们的变量确定为any。

例如下面判断是否时数字类型的函数:

function isNUmber(num:any) {
  return typeof num==="number"
}

我们在开发中应该尽量避免any的写法,因为这样会破坏极限状态下发动机的平衡,开个玩笑,这样会破坏系统的健壮性。

其中any有以下三个特点:

  1. 允许赋值为任意类型
  2. 允许访问任意属性和方法
  3. 变量在声明期间没有定义类型就会被识别为any类型

注意:未声明的变量虽然在一开始被识别为any类型,但是经过赋值后,ts会根据赋值类型来标识变量的类型,也就是我们的变量经过第一次赋值后就会将any类型变为我们被赋值的类型

8.void类型

void类型其实就是没有任何类型

通常用在没有返回值的函数上:

function warnUser(): void {
    console.log("This is my warning message");
}

申明为void的变量,只能赋予undefined和null

9.never类型

never类型实际上就是永远不存在的值,例如我们通过封装一些错误函数,它不需要返回,通常需要抛出一个异常就直接终止,又或是一些根本没有返回值的函数表达式或者是箭头函数的返回值类型。

// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
    throw new Error(message);
}

// 推断的返回值类型为never
function fail() {
    return error("Something failed");
}

// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
    while (true) {
    }
}

10.Object类型

Object表示非原始类型,也就是除了基本类型以外的值。这样一来我们就可以更好的去表达一些api

Ts的编译选项

首先我们应该清楚ts的代码目前是不能直接运行在浏览器中的,因为这就和我们的es6一样,同样需要编译.

我们最原始的方法是使用tsc进行编译单个文件,也可以直接使用-w进行文件的同步编译.但是这样难免会比较麻烦.为了解决这个问题,我们可以直接使用tsconfig文件进行配置编译选项.

当我们直接创建一个tsconfig.json文件时,再进行tsc就会发现,这个编译就是我们当前目录下的所有文件了。

它的配置选项其实最外层有这么几大模块。分别是include、exclude、extend、files、compilerOptions

**include:**用来设置编译哪些文件下的ts文件,将文件夹的路径通过数组一一列举出来,例如./src/**/*

**exclude:**用来设置哪些文件内容应该排除在外的,用法其实和include相似。这是可选的

**extend:**用来继承某些配置文件,因为我们知道配置文件通常是比较复杂的,当我们有多个配置文件时就应该分开写,例如可以将基本功能写在一个配置文件中,剩余的配置文件继承这个base即可。

**file:**这个 配置也是用来定义哪些文件需要被编译,不同于include的是,这里数组的内容跟的是文件名而不是目录的路径

**compilerOptions:**这个是ts的编译器选项,也是我们ts编译选项里面最为重要的一个。再compilerOption中又有一些属性,这些属性可以详细的配置我们编译器的规范。

  1. target:这是用来指定我们将ts的代码编译为哪种类型的js代码,到底是es3还是es6等等
  2. module:这是用来定义我们ts的模块化规范
  3. lib:指定我们需要使用的库
  4. outDir:这是指定我们js文件的输出路径
  5. outFile:这是将代码合并成一个文件
  6. allowJs:这个属性用来指定我们对js文件是否进行编译,因为我们某些功能需要使用js,我们依然要将这些js也进行编译到我们的目标文件夹下
  7. cheakJs:检查js代码是否符合语法规范,默认为false
  8. removeComment:编译后是否移除注释
  9. noEmit:不生成编译后的文件 ,默认为false
  10. onEmitOnError:当有错误时不进行编译,直接报错,避免有安全隐患的代码
  11. alwaysStrict:编译后是否使用严格模式,默认为false
  12. NoImplicitAny:不允许隐式的any类型,默认为false
  13. NoImpilcThis:不允许不明确类型的一个this(确定this是谁),默认为false
  14. strictNullChecks:严格的检查空值,如果有null值造成的风险再编译期就会报错,默认为false
  15. strict:所有严格检查的总开关

ts面向对象

关于类与继承的概念这里就不再进行记录了,这里只记录ts独有的一些特性以及功能

1.抽象类

实际上抽象类就是用来被继承的类,也就是父类,通常直接在类名之前用abstract修饰,并且抽象类是不能用来实例化的。

除此之外在抽象类中还有抽象方法的概念。所谓抽象方法实际上就是在抽象类中的某些方法,我们这个方法通常只是被定义一下,没有具体的内容,最后由子类实现重写这个抽象方法。抽象方法通常也是使用abstract来修饰的。

所以我们可以为抽象类总结出以下观点:

  1. 抽象类和其他类的区别不大,只是不能用来创建对象
  2. 抽象类就是专门用来被继承的类
  3. 抽象类中可以添加抽象方法
  4. 抽象方法只能定义在抽象类中,并且子类必须重写抽象方法。

我们下面就可以通过一个经典的继承案例来写一下抽象类。


abstract class Animal {
  name: string;
  constructor(name: string) {
    this.name = name
  }
  abstract printTheWork(): void
}

class Dog extends Animal {
  age: number;
  constructor(name: string, age: number) {
    super(name)
    this.age = age
  }
  printTheWork() {
    console.log('我是狗,我' + this.age);
  }
}

class Cat extends Animal {
  money: number;
  constructor(name: string, money: number) {
    super(name);
    this.money = money
  }
  printTheWork() {
    console.log('我是猫猫,我' + this.money);
  }
}
let tom:Dog=new Dog('tom',12);
let jack:Cat=new Cat('jack',18);
console.log(tom);
console.log(tom.printTheWork());//我是狗,我12
console.log(jack)
console.log(jack.printTheWork());//我是猫猫,我18

可以看出我们以上的代码我们有一个抽象的animal类,并且 由自己的抽象方法,然后接着两个子类cat和dog就需要实现我们的 抽象类,不然是会报错的。

2.接口

接口实际上就是定义了定义了一系列规范的的的角色。

他通常用来定义一个类的结构,用来定义一个类中应该包含哪些属性和方法,同时接口也可以当成类型声明去使用。

接口可以在定义类的时候去限制类的结构,这一点其实和抽象类有几分相似。

注意:

  1. 接口中的所有属性都不能由实际的值
  2. 接口只定义对象的结构,而不考虑实际值,在接口中所有的方法都是抽象方法

它的写法实际上也很简单,我们可以使用interface关键字来定义一个接口,然后可以在其他的类中使用implement来实现这个接口。注意接口是用来实现的。

我们可以再写一个例子:

interface animal{
  name:string;
  age:number;
  doSomeThing():void 
}

class Fish implements animal{
  name:string;
  age:number;
  constructor(name:string,age:number){
    this.name=name;
    this.age=age;
  }
  doSomeThing(){
    console.log(this.name,this.age)
  }
}

let yu=new Fish('敖丙',500);
yu.doSomeThing();

我们定义个animal的接口,里面定义了属性与需要实现的方法,也就是我们的结构,然后让fish类实现这个接口。得到以上结果。

3.属性的封装

我们知道在js中如果想改变一个类中属性的值是很容易的,直接拿到赋值就可以达到我们想要的效果,但是这种方式有利有弊,好处就是简单,坏处就是可能会造成一些安全的隐患,比如我们修改了的数据不属于我们范围内。

在ts中的解决方式就是使用新增的几个属性修饰符、private、public、protect。

**public:**共有属性,被修饰的属性可以在任意位置访问(修改)默认值

private:私有属性,私有属性只能在类的内部进行访问(修改)默认值。ps:这也是解决我们上面问题的方案,我们可以在类中添加方法使得私有属性进行被访问或者修改,还可以加一些逻辑限制

**protect:**受保护的属性,只能在当前类和当前类的子类中使用。

所以我们可以使用private来修饰我们的属性进行对属性的直接操作。如下代码:

class Car {
  private name: string = '福特';
  private time: string = '2021-12-23';
  getName() {
    return this.name
  }
  setName(value: string) {
    this.name = value
  }

  getTime() {
    return this.time
  }

  setTime(value: string) {
    this.time = value
  }
}
let car = new Car();
console.log(car.getName())
car.setTime('19991121')
console.log(car.getTime())

如上面代码所示,我们将属性私有化,再通过自定义方法进行修改访问。就可以达到安全访问的目的,比如如果我们设定日期的范围,如果超出我们的范围就可以选择不修改。

当然这种方式还是感觉挺麻烦的,ts还为我们提供了专门的set和get方法。同样是上面的例子,使用方式就是这样的。

class Car {
  private _name: string = '福特';
  private _time: string = '2021-12-23';
  get name() {
    return this._name
  }
  set name(value: string) {
    this._name = value
  }
  get time(){
    return this._time
  }
  set time(value:string){
    this._time=value
  }
}
let car = new Car();
console.log(car.name);
car.time='19991121';
console.log(car.time)

4.泛型

其实泛型的意义就是让我们在不知道类型时又不想使用any(越过类型检测)时的一个解决方案。

写法也其实很简单。比如我们现在需要定义一个两个参数的函数,其中我们不确定参数类型,我们就可以这样写:

function doSomeThing<T, W>(age: T, name: W): T {
  console.log(name);
  return age
}

console.log(doSomeThing(123,'234'))

初次之外泛型不仅仅可以指定基本类型,还可以为其指定一些类、接口、等等