后端小白的TypeScript入门学习笔记

写在前面

本博文仅作为个人学习过程的记录,可能存在诸多错误,希望各位看官不吝赐教,支持错误所在,帮助小白成长!

一、TypeScript入门了解

1.1、什么是TypeScript

image-20210528141517313

学习TypeScript前,我听过的关于它最多的一句话就是:TypeScript是JavaScript的超集!

TypeScript在JavaScript的基础与ES标准之上,加上了接口、类型限定、泛型等功能,强化了代码静态检查。更易于我们编写代码,但是TypeScript语法的代码目前无法直接运行在浏览器上,需要将TypeScript代码通过编译器(tsc)转化为JavaScript代码然后再进行使用!(TypeScript中可以使用所有JavaScript语法)

TypeScript由微软开发并开源,在2012.10发布了TypeScript第一个公开版本,正式版于2013.6.19发行!TypeScript的作者是***安德斯·海尔斯伯格***,C#的首席架构师。

1.2、快速上手

安装TypeScript

安装TypeScript:

npm install -g typescript

验证安装:(当前笔记使用版本为4.3.2)

tsc -V

代码文件编写

创建一个文件夹作为代码目录:

  • 创建index.ts文件
  • 创建index.html文件

index.ts

function sayHi(speaker: string) {
  console.log(`${speaker}: Hello TypeScript!`)
}

sayHi('sakura');

这里我们使用了一个ts的语法:speaker: string,对函数参数类型做了限定!此时如果你在调用sayHi函数时,不指定参数或者参数类型不是string时,就会报错!(但是编译依然会通过!)

如果此时我们直接在html中引入ts代码文件,并在浏览器中运行,浏览器是会报错的!因为含有ts语法代码是无法直接在浏览器中运行的!(如果没有使用ts语法,只是基础的js代码,依然可以使用)此时需要对ts代码进行编译!

编译(ts -> js)

我们安装TypeScript时,同时为我们安装了TypeScript代码的编译器tsc,使之转化为JavaScript代码。

执行以下命令,将ts代码编译成js代码

tsc ./index.ts

然后命令执行所在目录就会生成编译后的js代码,然后我们在Html文件中进行引入即可!

1.3、TypeScript自动编译(vscode)

首先在代码目录中执行命令:

tsc init

会生成一个tsconfig.json文件,文化中包含了一些编译配置选项,其中有一个outDir默认值是./即编译后的文件是在命令执行的所在目录。你可以按需修改为其他位置,这里我修改为./js/

然后我们使用ctrl+shift+b,打开task运行面板,选中tsc watch(如果有多个项目目录,注意匹配当前代码目录的配置文件!)

如果修改了快捷键,可以在【菜单栏】-> 【Terminal】-> 【Run Task …】-> 【typescript】中找到任务并执行。

此时tsc会在后台监控目录下的ts文件,如果发生了变化将实时对其编译!

1.4、搭建Webpack项目

环境准备

当项目工程化以后,我们如何在webpack中配置TypeScript自动编译?!

首先我们使用npm init -y对工程进行初始化(如果你全局安装了webpack和webpack-cli,可以省略此步骤)

然后执行npx webpack init

npx会优先使用项目本地依赖包中的webpack,并且会临时安装可执行依赖。

如果全局安装直接使用webpack init

然后开始进行webpack项目配置:

  1. 选用typescript
  2. 启用webpack-dev-server
  3. 启用简化HTML文件创建和bundle引入
  4. 样式文件如果没有选择none
  5. 不启用Prettier
  6. 最后选择覆写package.json

image-20210528153811934

然后静静等待配置文件生成即可!

基础测试

完成后,会发现我们ts转换js的ts-loader已经帮我们安装了,npm脚本命令也已经为我们配置好了,HttpWebpackPlugin也已经配置好了。

image-20210528155510132 image-20210528155425938

如果还需要其他配置,可以在webpack.config.js中进行修改!例如在output中设置clean:true,开启dist文件夹清理【对webpack不熟悉的,复习一下webpack,再按需对配置文件进行修改~】

你先你可以先尝试在src/index.ts中写一些ts语法的代码,然后命令行中执行npm run build:dev(开发环境打包),然后dist目录中就是打包构建的内容了,ts代码也被成功编译为js代码,并自动引入了!

现在你可以使用npm run serve启动webpack-dev-server,启动后我们可以实现代码的热部署,每次我们代码变化后,都会自动进行build,然后我们在浏览器上刷新就可以看到最新的代码效果了!!

二、TypeScript特性

2.1、基础类型

TypeScript相较于JavaScript变化最明显的,最容易感受到的就是加上了类型限定,通过x:number此类形式为变量/属性设置类型,一旦声明时类型确定就不再改变,妥妥的静态类型语言!这也就是为什么我们在编写JavaScript代码时针对类型的错误,我们不便于排查,但是换上TypeScript,就像写Java一样,代码逻辑上一点纰漏都会被排查出来!

number、string、boolean

以上几种类型,但凡学过任何一门语言应该都知道,直接略过!

说明一点:在JavaScript和TypeScript中,所有的数字都是浮点数,所有没有整型和浮点型只分,同属于number!TypeScript支持二、八、十、十六进制字面量:

let decLiteral: number = 6;
let hexLiteral: number = 0xf00d; // 十六进制
let binaryLiteral: number = 0b1010; // 二进制
let octalLiteral: number = 0o744; // 八进制

Array、Tuple

数组应该都很熟悉,直接上代码:

// 声明数组
let numbers: number[] = [1, 3, 5, 7, 9]
console.log(numbers[2]); // 5

// 使用Array+泛型声明数组
let numbers2: Array<number> = [2, 4, 6, 8, 0]
console.log(numbers2[2]); // 6

在TypeScript中,当你为数组元素类型做了限定后(即使用了arr: number[]arr: Array<number>)数组中只能放置对应类型的数据

在JavaScript中,数组没有元素类型的限定,多种类型的元素允许出现在一个数组中:

let arr = [1, 'Hello', true, 3.1415926]

当然这种操作也是可以用TS语法实现的,不过需要一个特殊的类型Any,这个后面再说。

Tuple元组,可以将看做是一个指定大小,并且指定每个位置元素类型的数组。在声明时就确定了元组的大小以及各个位置元素的类型:

let tuple: [number, string, boolean] = [123123, 'sakura', true]
console.log(`id :${tuple[0]} | name: ${tuple[1]} | isMan: ${tuple[2]}`);

使用元组,我们可以将看似无关数据进行组合,形成一个整体

enum

年少不知枚举好,错把if-else当成宝!

使用枚举,我们可以为复杂的数据取名字,方便我们在使用时进行调用。

创建一个枚举类型:

enum Color {
  red,
  blue,
  green
}

因为此时,我们还没有为枚举中的元素进行赋值,所以元素会默认从0开始编号,将其编号作为自己的元素值:

console.log(Color.red); // 0
console.log(Color.blue); // 1
console.log(Color.green); // 2

当你某个元素赋number类型值后,后面的元素会重新以此数值为起点重新计算编号:

enum Color {
  red,
  blue = 99,
  green
}

console.log(Color.red); // 0
console.log(Color.blue); // 99
console.log(Color.green); // 100,从99开始计算编号

但是如果你为元素赋值为字符串类型,那么其后的元素都必须进行赋值!(只有数字类型的枚举可以计算元素值!并且只能使用字面量进行赋值。)

但我们是希望用Color.red来代替一个复杂的数据,我们可以给每个元素都指定数据值:

enum Color {
  red = 0xff0000,
  blue = 0x0000ff,
  green = 0x00ff00
}

更厉害的是,你可以使用元素值反过来去取出泛型元素名:

console.log(Color[0xff0000]) // red

undefined、null、void

在TypeScript中,undefined和null,都有他们各自的类型:undefinednull,很少会将他们设置为数据类型,在严格模式下,undefined和null只能赋值给他们自己本身或者void

let data: undefined = undefined;
data = null; // 错误!

let data2: null = undefined; // 错误!
data2 = null;

let data3: void = undefined;
data3 = null // 错误!

let data4: number = undefined; // 错误!

当你关闭了严格模式后,null、undefined可以作为任何类型的子类,那么以上代码中的写法就都可以通过检查!

ts开启严格模式

tsconfig.json文件中修改“compilerOptions.strict”选项值

{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "noImplicitAny": true,
    "module": "es6",
    "target": "es5",
    "allowJs": true,
    "strict": true // true开启、false关闭
  },
  "files": ["src/index.ts"]
}

void类型,表示没有任何类型!通常用于函数返回值类型的设定!

any

与void表示相反,any表示任何类型。通常用于类型不确定的变量、参数设置。

例如上面说的实现多类型元素出现在同一数组:

let arr:any[] = [1, 'Hello World', true, undefined]
// 或者
let arr:Array<any> = [1, 'Hello World', true, undefined]

当我们声明了一个any类型的变量后,任何类型的值都可以直接赋给此变量

let variable: any
variable = 1
variable = 'Hello'
variable = true
variable = undefined

使用了any后,写ts代码就和js代码一样的,没有类型限制。但是会牺牲TypeScript的静态类型检查功能!

object

object表示非基本类型(即除number、string、boolean、undefined/null【仅限严格模式下,非严格模式下undefined和null可以赋值给object类型变量】)

let obj: object = 1 // error
obj = 'Hello' // error

obj = undefined // 严格模式下 error
obj = null // 严格模式下 error

obj = {name: 'sakura', age: 20} // bingo!

2.2、高级类型

联合类型

当函数的参数类型不确定时,但是你已知参数的类型范围时,就可以使用联合类型arg: A|B它表示参数可能为类型A或者类型B。如果这里使用any,那么就失去了类型限定的意义,我们希望调用者传入的参数是我们预期之内的!

function returnNumber(arg: number | string): number {
  if (typeof arg === 'number') {
    return arg
  } else {
    return (<string>arg).length
  }
}

console.log(returnNumber(99)) // 99
console.log(returnNumber('Hello World')) // 11

类型断言

上面联合类型中,我们使用一段代码:(<string>arg).length。可能感觉到是在将参数转换为string类型。我们将这种转换方式称为类型断言。意在告诉编译器,”我比你更懂这个数据现在是什么类型“。你帮我装换成对应的类型就行。

除了使用尖括号写法,还可以使用关键字as进行转换:

(<string>arg).length
// 或者
(arg as string).length

这两个东西,后面我们还会遇到,以后慢慢说。

接口

在Java中,接口可以视为是对象属性、方法的抽象。或者我们可以将它看做为约束、规范、能力。它甚至可以当做类型来使用!

基本使用

使用关键字interface即可创建一个接口。

interface ISpeak {
  name: string
  speak: () => void // 函数类型,请查看函数笔记
}

接口旨在对功能进行扩展,你可以使用implements实现接口,扩展类的功能。你也可以将接口当作类型使用:

let dog: ISpeak = {
  name: '旺财',
  speak: () => {
    console.log(`汪汪汪`)
  }
}

dog.speak() // 汪汪汪

let dog2 = {
  name: '阿呆',
  speak: () => {console.log(`嗷嗷嗷?`)}
}

dog = dog2

dog.speak() // 嗷嗷嗷?

只要对象的结构满足接口的结构(属性对应,不多不少!),就可以视为是此接口类型的对象!

接口是可以使用extends进行继承的,这点和Java也一样。

可选属性

当接口中有些属性,不是强制需要时,可以使用?修饰将其设置为可选属性:

interface ISpeak {
  name: string
  age?: number // 可选属性
  speak: () => void
}

let student:ISpeak = {
    name: 'sakura',
    age: 20,
    speak: () => { console.log('我要学习!!') }
}

// 不想透露年龄
let stranger:ISpeak = {
    name: 'Philip',
    speak: () => { console.log('我很忙!!') }
}

只读属性

当接口中的属性不想在外部进行修改时,我需要将其使用readonly将其设置为只读。(而不是const!!const只能用于修饰变量!属性修饰需要使用readonly!)

interface ISpeak {
  readonly name: string
  age?: number
  speak: () => void
}

let student:ISpeak = {
    name: 'sakura',
    age: 20,
    speak: () => { console.log('我要学习!!') }
}

student.name = 'Philip' // ERROR

注意一定要声明类型为ISpeak,如果只是结构相同,并不会生效:

let stranger = {
    name: 'xiaoming',
    age: 18,
    speak: () => {
        // ...
    }
}
stranger.name = 'xiaogang' // 不会报错,因为没有显示说明变量类型为ISpeak, 无法进行检查!

函数类型

除了在接口中定义属性外,还可以对行为进行抽象,也就是本节要提到的函数类型。使用一个函数类型描述一个行为。

例如上述代码中的speak: () => void,表示一个speak是一个行为抽象,无参数,返回值是void。还可以写成speak(): void;,当接口中只有一个函数时,甚至可以省去函数名。写成(): void

__后续学习泛型以后,函数类型的描述将更加多样!__例如func: <T, K>(arg1:T, arg2:K) => T

在JavaScript中我们已经广泛使用类来完成面向对象编程,在TypeScript中类的特性并没有改变。我们还是可以像过去一样使用类。不过TypeScript中多了接口类型后,便于我们使用接口来扩展类的功能。

interface

interface IFly {
  fly(): void
}

class

class Animal {
  name: string
  constructor(name: string) {
    this.name = name
  }
}

class Cuckoo extends Animal implements IFly {
  constructor(name: string) {
    super(name)
  }

  fly(): void {
    console.log("起飞,布谷布谷")
  }
  
  shout() {
    console.log("我是一只布谷鸟,布谷布谷")
  }
}

强制:子类的构造函数中一定要显式调用父类的构造函数!!

构造函数

在定义类的构造函数时,我们可以为某些参数指定默认值

class Cuckoo extends Animal implements IFly {
  constructor(name: string = "布谷鸟") { // 为name设置默认值
    super(name)
  }

  fly(): void {
    console.log(this.name + ": 起飞,布谷布谷")
  }
  
  shout() {
    console.log(this.name + "我是一只布谷鸟,布谷布谷")
  }
}

new Cuckoo('库库').fly() // 库库:起飞,...
new Cuckoo().fly() // 布谷鸟: 起飞,布谷布谷

你也可以为成员属性赋默认初始值:(接口中不适用!!)

class Animal {
  name: string = "animal" // 属性初始值
  constructor(name: string) {
    this.name = name
  }
}

你还可以将类属性的声明放到构造函数的参数中完成:

class Animal {
  constructor(readonly name: string = "animal", private id: number) {
    this.name = name
  }
}

访问权限修饰符

privateprotectedpublic(默认),在Java中我们已经学习过他们三者的对访问范围的界定了。稍微有点区别的是Java中默认的访问修饰符不是public。

public是公有的访问修饰符,我们就不过多说明了。

private

私有访问修饰符,修饰的属性和方法只能在类中进行使用,我就不就行验证了。一个关于TypeScript语言特点的问题需要说明一下:

TypeScript使用的是结构性类型系统。 当我们比较两种不同的类型时,并不在乎它们从何处而来,如果所有成员的类型都是兼容的,我们就认为它们的类型是兼容的。

class Cat {
  public name: string

  public constructor(name: string = "Tom") {
    this.name = name
  }
}

class Dog {
  public name: string

  public constructor(name: string = "Bob") {
    this.name = name
  }
}

let dog: Dog = new Dog()
let cat: Cat = new Cat()

dog = cat // what?!

上面代码中你感到疑惑的点就是:明明是两个类,并且没有任何的继承关系,但是居然可以互相引用(Cat引用居然可以接受一个Dog实例)!?这就是上面所说的结构性类型系统!你是什么类型我并不关心,只要结构相同我认为你们可以相互兼容!(这里的兼容,指的是可以相互交换引用,并不是说两个类无差别!)

但是这个规则只适用于类中没有使用privateprotected访问修饰符!

当使用了private、protected访问修饰符后,在结构相同的情况下,两个对象的private、protected成员属性必须来自于同一处声明!才能认为两个类型是相互兼容的!

什么叫做来自于同一处声明呢?!
打个比方:现在有一个Animal类,其有一个private属性name。然后又创建一个Cat类,并且extends Animal。那么当你创建的Animal对象和Cat对象,他们的name属性就来自于同一处声明。(即Animal类中private name:string

相反,如果你创建了一个Person类,没有继承Animal类,并且声明了一个name属性,那么创建的Person对象中的name和Animal、Cat对象的name就不是来自于同一处声明,固然Person与Animal、Cat不是类型兼容的!

class Animal {
  private name: string
  constructor(name: string = "Animal") {
    this.name = name
  }
}

class Cat extends Animal {
  
  // 从父类Animal继承name属性

  public constructor(name: string = "Tom") {
    super(name)
  }
}

class Person {
  private name: string
  constructor(name: string = "Fitz") {
    this.name = name
  }
}

let animal: Animal = new Animal()
let cat: Cat = new Cat()
let person: Person = new Person()

cat = animal // bingo
/*

cat = person // error
animal = person // error
person = cat // error

*/
protected

相较于private更宽松一点,protected修饰的属性/方法可以在其派生类中进行使用

只读属性

和接口中声明只读属性一样使用readonly修饰符,结合属性默认值使用效果更佳!

存取器

在Java中我们使用getter/setter来对属性值进行修改和访问!在TypeScript中一样也可以,不过有些特别!

我们设置存取器的时候,就是希望外部只能通过我们预留的get/set方法来对属性进行访问与修改,而不是随意的使用对象直接通过属性名进行访问到属性并进行修改。在TypeScript中预留了两个关键字getset

我们现在就来看看如何在ts中写getter、setter:

class Person {
  private _id: string

  constructor(id: string) {
    this._id = id
  }

  get id(): string {
    return this._id
  }

  set id(value: string) {
    this._id = value
  }
}

let sakura = new Person('420xxx20000817xxxx')
console.log(sakura.id);

sakura.id = '444xxx20000718xxxx'
console.log(sakura.id)

你会发现get、set调用并不像我在Java中那样直接用方法完成,而是通过属性名。此时如果你不知道类的结构时,你丝毫感觉不到get、set方法的存在,一个private属性,如果我想让你在外部访问或者修改,我就可以设置一个get/set方法。

如果一个private属性只设置了get,那么就意味着它是一个只读的属性!!

静态属性

熟悉Java语言的同学,应该不陌生。静态的属性与方法并不属于某一个实例,而是属于类的!所有实例都共用!使用static修饰即表明属性、方法是静态的

抽象类

对类结构的一种抽象描述,不可进行实例化,声明时使用abstract class。如果说接口的作用是功能扩展,那么抽象类作用就是对事物进行抽象,由其他类进行继承完善细节。


函数

和JavaScript中的函数使用大体相同,一样有闭包概念。我们用一个简单的例子快速回忆JavaScript中的函数:

// 匿名函数
let addFunc = function(x, y) { 
    return x + y 
}

在TypeScript中加入类型限定后,我们可以通过表达式来表示一个函数类型:

let addFunc:(x:number, y:number) => number

function add(base:number, incr:number) {
    return base + incr;
}

addFunc = add
console.log(addFunc(2, 4)) // 6

可选参数、参数默认值

可选参数必须跟在必要参数后!

function incr(base: number, step?: number): number {
  if (step !== undefined) {
    return base + step;
  }
  return base + 1;
}

console.log(incr(3, 3)) // 6
console.log(incr(3)) // 4

带默认值的参数,当函数调用时传入参数为undefined(或者未传入时)时,如果设置了参数默认值会使用参数默认值!

function incr2(base: number, step: number = 1): number {
  return base + step;
}

console.log(incr2(4, 4)); // 8
console.log(incr2(4)); // 5
console.log(incr2(4, undefined)); // 5

当带默认值的参数写在前面时,如果想使用参数默认值就只能传undefined!否则会影响后面的参数匹配!

默认值参数和可选参数不能同时出现在一个参数身上!

x?:number = 0

剩余参数

剩余参数只能写在函数参数的末尾!标志符是...,并且类型一般都是数组!!

function printNumbers(arg1: number, arg2: number, ...rest: number[]): void {
  console.log(arg1);
  console.log(arg2);
  rest.forEach((value) => {console.log(value)});
}

printNumbers(1, 2, 3, 4, 5, 6, 7, 8, 9)

函数重载

在Java中,我们进行函数重载,都是进行额外的实现。每个重载的函数都会有自己的功能实现代码,即使只是调用了其他方法。但是在TypeScript中,函数的重载是通过定义重载完成,然后借助联合类型。

function compare(arg1: number, arg2: number): void
function compare(arg1: string, arg2: string): void

function compare(arg1: number | string, arg2: number | string): void {
  if (typeof arg1 == 'number' && typeof arg2 == 'number') {
    if (arg1 > arg2) {
      console.log(`${arg1} > ${arg2}`)
    } else {
      console.log(`${arg1} <= ${arg2}`)
    }
  }

  if (typeof arg1 == 'string' && typeof arg2 == 'string') {
    if ((arg1 as string).length > (arg2 as string).length) {
      console.log(`${arg1} > ${arg2}`)
    } else {
      console.log(`${arg1} <= ${arg2}`)
    }
  }
}

compare('Hello', 'TypeScript') // Hello <= TypeScript
compare(10, 11) // 10 <= 11

重载函数的实现并不参与类型检查,只有重载定义的函数才会被检查!!

compare(10, 'Hello') // ERROR
compare('Hola', 10) // ERROR

泛型

在Java中我们使用泛型,能够让方法针对多种类型的参数值都可以应对!TypeScript也是如此,使用泛型后一套代码逻辑可以在多种类型的参数下进行利用。大大提供了代码的复用率!

在接口上使用泛型:

interface KeyValueData<K, V> {
    key: K
    value: V
    compareTo(other:K):boolean
}

其他更高级的用法,可以参考官方文档,或者学习Java语言中泛型的使用!

声明文件

当我们使用第三方库时,我们需要引入其声明文件后,才能获得其对应的代码补全,参数提示灯功能。

声明文件中的声明语句:

declare var JQuery: (selector: string) => any

一般这种声明语句会集中放置在一个扩展名为.d.ts的文件中(即声明文件),TypeScript编译器会自动扫描检查项目中所有的声明文件

当我们需要对应库的代码提示时,可以使用npm下载对应的声明文件。

例如下载JQuery的声明文件

npm install @type/jquery --save-dev

console.log(${arg1} <= ${arg2})
}
}
}

compare(‘Hello’, ‘TypeScript’) // Hello <= TypeScript
compare(10, 11) // 10 <= 11


**重载函数的实现并不参与类型检查**,只有重载定义的函数才会被检查!!

```typescript
compare(10, 'Hello') // ERROR
compare('Hola', 10) // ERROR

泛型

在Java中我们使用泛型,能够让方法针对多种类型的参数值都可以应对!TypeScript也是如此,使用泛型后一套代码逻辑可以在多种类型的参数下进行利用。大大提供了代码的复用率!

在接口上使用泛型:

interface KeyValueData<K, V> {
    key: K
    value: V
    compareTo(other:K):boolean
}

其他更高级的用法,可以参考官方文档,或者学习Java语言中泛型的使用!

声明文件

当我们使用第三方库时,我们需要引入其声明文件后,才能获得其对应的代码补全,参数提示灯功能。

声明文件中的声明语句:

declare var JQuery: (selector: string) => any

一般这种声明语句会集中放置在一个扩展名为.d.ts的文件中(即声明文件),TypeScript编译器会自动扫描检查项目中所有的声明文件

当我们需要对应库的代码提示时,可以使用npm下载对应的声明文件。

例如下载JQuery的声明文件

npm install @type/jquery --save-dev
posted @   5akura  阅读(396)  评论(0编辑  收藏  举报
努力加载评论中...
点击右上角即可分享
微信分享提示