震惊!有关一个dotaer学习TypeScript这件事。。。。

629 阅读8分钟

1.TypeScript 介绍

TypeScript 是什么?

简单来说,TS就是Javascript的超集,超集就是数学上的那个超集的意思,其实就是以JS为基础,增加了许多更人性化和更方便的功能,如图所示,JS有的,TS都有,所以TS代码中,可以编写任意JS代码。

TS.jpg

TypeScript 相比 JavaScript 的优势是什么?

1.强大的类型系统,提升了代码的可维护性。

2.支持ECMAScript最新语法

3.类型推断机制。

4.编写程序中的任何地方都有代码提示,编码体验极强。

TypeScript 趋势

尤大在Vue3中已经使用TS进行重写,Angular默认就支持TS,而目老牌框架react早就与TS完美配合,市面上大中型项目中TS已经成为必不可少的一部分。

2.TS的类型系统

首先我们看这段JS代码

let a = 1
 
a.toFixed()

这段代码暂时看来没有什么问题,但是如果你对变量a的类型进行了改变,例如

let a = 1

a = '1'

a.toFixed()

那么你就会发现,当代码跑起来的时候,会显示以下错误

error.png

这是因为toFixed方法只能作用于数值类型,但是当你把变量a的类型转换成字符串时自然会报错。

再让我们看看同样的代码在TS中会如何

// ts的写法规定了变量a必须是number类型
let a:number = 1

a = '1'

a.toFixed()

当你运行这段代码时,不,你根本不需要运行这段代码,你就会发现

error2.png

是的,已经报错了,告诉你不能把string类型赋值给number类型的变量。

这就是TS和JS本质上的区别,TS是静态语言,JS的动态语言,而静态语言编译时就会进行判断,动态语言只有在运行时才会进行对类型的判断。

3.Type Script 常用类型

3.1 类型注解

// TS的类型其实就是在变量名后添加一个:并且跟上一个数据类型,如此就可以约束变量的类型。
let num: number = 10

3.2 Type Script 常用基础类型

TS的常用类型简单来说就是两种,一种是JS已有的原生类型,一种是TS新增类型

JS已有类型

简单类型:number / string / boolean / null / undifined / symbol(ES6新增数据类型,用的不多,具体不再阐述

对象类型:object ( 数组、对象、函数等对象都属于此 )

TS新增类型

联合类型、自定义类型(类型别名)、接口、元组、字面量类型、枚举、void、any等

我们先来看看JS已有类型的定义方式。


let hero: string = '灰烬之灵'

let age: number = 22

let ultimateSkill: boolean = true

接下来就是对象类型的定义方式。

在对象类型中,每个具体的对象,都有自己的类型写法。

个人觉得这一点挺关键的,毕竟在原生JS中,无论是数组Array还是对象Object,用typeOf类型判断的时候,返回的都是Object。

此处先说说数组Array的写法。

Array


// 第一种写法, 类型 + [], 推荐, 常用写法.

let arr: string[] = ['风暴之灵', '灰烬之灵', '大地之灵', '虚无之灵']

// 第二种写法, Array + <类型>, 不推荐, 因为在react中script标签中是可以写标签的, 所以此种写法与标签的尖括号冲突.

let arr2: Array<string> = ['蓝猫', '火猫', '土猫', '紫猫']

那写到这里就有小伙伴要问了,如果我想要一个数组里同时存在字符串和数字或者更多类型怎么办呢?

当然有办法了


let arr: (number | string)[] = ['敌法师', 30000, '一秒躺'] 

这就是TS的联合类型,使用 | 隔开两个或多个类型。

接来下我们来看对象类型中,函数类型的写法。函数类型有两种,我们先说第一种。

Function


function add(num1, bum2) {
    return num1 + num2
}

这是一个最基本的函数。

首先来说一下,对于函数来说,最重要的其实就是函数的参数和返回值,那么其实函数类型要约束的也是函数的参数和返回值。

说到这里,肯定已经有聪明的小伙伴想到了,函数的参数,其实也就是一种变量,没错,所以函数的参数和普通变量是一种语法,如下


function add(num1: number, bum2: number) {
    return num1 + num2
}

那么返回值该如何约束呢?


function add(num1: number, bum2: number): number {
    return num1 + num2
}

在参数后面加上约束类型就好啦。

当然如果函数是一个表达式时,写法是一样的


const add = (num1: number, bum2: number): number => {
    return num1 + num2
}

这就是函数类型的第一种写法,分别单独参数返回值定义类型,接下来看第二种。

第二种就是同时约束函数的参数返回值类型。

这种写法与普通变量的语法类似,但是只有当函数是表达式时,才可以使用。


const add: (num1: number, bum2: number) => number = (num1, num2) => {
    return num1 + num2
}

其实肯定会有人想问,实际应用中,使用函数时无需返回值也可完成某些功能,这时候该怎么办呢。

如果上面这段函数没有返回值,运行起来会怎么样?


const add: (num1: number, bum2: number) => number = (num1, num2) => {
    console.log('直接进行一百个勤的酬')
}

没错,的确会报错,因为你规定了返回值的类型,但并没有返回值。

所以这时候就用到了TS的新增类型void


const add: (num1: number, bum2: number) => void = (num1, num2) => {
    console.log('ti10 LDG必胜')
}

如上,这段函数并没有返回值,但是代码并不会报错

void的含义,就是空,可以使一个函数不需要返回值。

一定要跟null类型分开,void只能用来指定没有返回值的函数,而null是给变量指定类型。

说到没有返回值的函数,那肯定也有参数可传可不传的函数咯,定义方法如下


const add: (num1?: number, bum2?: number) => void = (num1, num2) => {
    console.log('ti10 LDG必胜')
}

在参数后面和冒号前面加上一个问号,就指定了这个参数未非必传参数,但是要注意,非必传参数必须在最后并且非必传参数后面不可存在必传参数

到这里,函数类型就算是告一段落了,接下来我们来看看对象类型object该如何去约束。

Object

JS中的对象,其实就是属性的方法的一个集合,只不过相对于数组来说,对象中有key值,也就是每一个属性和方法的名字。


let obj = {
    name: '萧瑟',
    age: 15,
    stageName: function() {
        console.log('仙贝')
    }
}

那么在TS中的对象类型,就是去描述一个对象中的每个属性或者方法的类型。


let obj: {name: string; age: number; stageName(): void} = {
    name: '萧瑟',
    age: 15,
    stageName: function() {
        console.log('仙贝')
    }
}

与其他类型规范不同的是,此处需要用 ; 隔开,而不是逗号,并且外层需要用大括号 {} 包裹,而不是小括号。

在对象类型中,如果方法有参数,那么和函数的定义相似,stageName(num1: number, num2: number): void

也可以使用箭头函数,stageName(num1: number, num2: number) => void

并且也是可以规定参数的可传可不传,与函数类型相同,stageName(num1: number, num2?: number) => void

接口(interface)

接口呢,其实就是一种针对于对象类型的封装,在某个对象反复使用的时候,就可以使用接口(interface)来进行一个类型集合的封装,来提高复用率减少代码量。

// 使用关键字interface, 此处变量名为任意合法就可
// 这里使用多行来分开写, 如果与平常定义类型同样在一行写, 需要加分号.
interface Person {
    name: string
    age: number
    stageName(): void
}

// 使用时与普通定义类型相似
let obj: Person = {
    name: '莫言',
    age: 18,
    stageName() {
        console.log('nothing to say')
    }
}

那么这个接口,和类型别名,有啥区别呢?

类型别名和接口,同样都可以为对象进行类型封装,但是接口只能针对于对象进行,类型别名可以为所有类型进行封装。

平常使用的话,在对象这块,用啥都可以。

接口的继承

实际工作中,可能会出现多个对象都有相同的属性,但是又存在不同的属性,并且在使用接口时,多个接口有重复相同的类型定义,这时就用到了接口的继承,可以更有效的减少代码量。

// 当两个接口有重复属性时, 就可以使用继承.
interface Person1 {
    name: string
    age: number
    stageName(): void
}
interface Person2 {
    name: string
    age: number
    stageName(): void
    rank: string
}

// ========================================================================================

interface Person1 {
    name: string
    age: number
    stageName(): void
}

interface Person2 {
    level: number
}

// 使用关键字extends, 这里表示Person2继承Person1和Person2的所有定义并且新增一个类型为string的rank属性.
// 这里我直接写的多个同时继承的写法, 当然你也可以单个继承.
interface Person3 extends Person1, Person2 {
    rank: string
}

元组(tuple)

元组是用来解决特殊数组类型定义的一种方案,因为普通数组定义,比如数值数组number[ ],虽然可以规定数组中必须全为数值类型,但是规定不了数组的数量以及特殊索引的类型。

// 没啥好说的, 就是对单个索引进行定义, 实际用处太少了, 一般来说数组都是相同类型的集合.
let person: [number, number] = [123, 456]

先讲到这里吧。。。等什么时候把泛型搞懂就来给大家讲泛型。斯米马赛。