前言
按照TS官网的介绍,ts是一门javaScript的超集。所谓超集意思就是,js有的东西,我ts有,js没有的,我ts还有,那到底ts提供给了我们哪些额外的东西了呢,先让我们看下js这门语言的简单描述,然后一切就明白了。
js的语言特点
JavaScript是一门动态弱类型语言。
熟悉js的同学知道,当我们声明了一个变量后,比如赋值了一个字符串,但是后续仍然可以将其再赋值成其它类型的数据,这个就是弱类型的表现。
// 先声明一个字符串
let a = 'string'
// 后重新赋值成数字,这里是正常运行,不报错
a = 123
相反那些,声明后不能修改变量值类型的语言,就是强类型语言。
而动态性是指,只有在执行阶段才会确定所有变量的类型,我们知道,js是一门脚本语言,不需要事先编译,运行js代码时,js引擎会边编译边执行。
// 因为入参不确定,所以这个js函数只有在执行时,才能知道返回值是什么类型
function add(x,y){
return x + y
}
add(2,4) // return 6 返回number类型
add('a','b') // return 'ab' 返回string类型
那么静态类型语言在编译阶段就会确定所有变量的类型。
js痛点
讲一下我在实际使用js的遇到的一些问题
- 项目庞大、代码量增多后,在后期维护阶段,对于一个变量的类型可能会不十分清楚。常常发生,调用一个变量的方法时,结果该变量值为null,然后报错的情况。
- 联调接口时,后端会返回一个层级较深,属性较多的对象,而我在使用它写业务代码时,只能边看着接口文档,才能知道里面都有什么东西,效率上有所影响。
- 接口的返回数据,没有类型约束,使用时常常发生TypeError。
TS提供的功能
- 我认为ts就是将js变成了一门静态强类型的语言,它提供了类型约束,它可以帮助我们重塑类型思维,减少代码的出错。
- 借助vscode的代码提示功能,能够提升开发的效率。
TS的使用
因为编写ts并执行,需要新建npm工程项目,以及构建工具的打包与编译,但这里并不介绍相关的内容,仅介绍下ts的基础用法。
基础类型
对于js原有的那些数据类型,在用ts声明时,只需在变量后加一个冒号以及类型名即可。
// 字符串类型
let name:string = 'typescript';
// 数字类型
let age: number = 18;
// 此时如果给age赋值其它类型,就会报错
age = '18' // Type 'string' is not assignable to type 'number'.
// 布尔类型
let isFlag:boolean = false
// Null类型
let isNull:null = null
// undefiend类型
let isUnde:undefined = undefined
// symbol类型
let id:symbol = Symbol(1)
// 对象类型
let peolpe:object = {
id
name,
age
}
// 数组类型,定义一个数组,且组内成员的类型为数字
let list:number[] = [1,2,3]
在实际使用中,其实单独的ts的null、undefined不是很常用,因为声明一个null或者undefined的变量没有意义,这种数据类型的声明也只是在js中使用,比如程序初始时,先声明一个null的变量,后续的代码逻辑中再改成其它数据类型。
除了这些数据类型,ts还新增了元祖、枚举类型。
其中枚举类型确实是个非常有用的类型,它可以为一个值赋予一个有意义的描述。
比如某些字段,有多个值,每个值有不同的含义,但如果我们直接使用这些值,来区分并写代码逻辑的话,后续维护时,会比较麻烦,因为你在编写代码前需要先搞明白这些值的含义。
js代码
// 例如这个字段,它有三个值 1,2,3,分别代表学生、家长、老师
let userType
switch(userType){
case 1:
// 学生用户的代码逻辑
case 2:
// 家长用户的代码逻辑
case 3:
// 老师用户的代码逻辑
}
就这上述这段代码,如果没有注释的话,过一段时间看,可能你就不知道这些数值代表的是啥意思了,但用ts的枚举的话,就很好的解决问题了。
ts代码重写
enum UserType {
student = 1,
parents = 2,
teacher = 3
}
let userType:UserType
switch(userType){
case UserType.student:
// 学生用户的代码逻辑
case UserType.parents:
// 家长用户的代码逻辑
case UserType.teacher:
// 老师用户的代码逻辑
}
这样是不是就一目了然了,增加了代码的可读性,后期维护也很容易。
接口
接口是前端之前没有过的一个概念,在基础类型中,我们只是约束了一个变量的类型,但是,在实际的开发中,我们会组织成一些具有结构的数据,而接口的作用就是对这些有结构的值进行类型约束,它也被叫做鸭式辨型法,意思就是如果一个东西,长的像鸭子,叫声像鸭子,那它就是鸭子。
接口可以约束对象、函数、和类的结构。
举个例子,就清楚了
对象接口
// 定义一个约束对象的接口,
interface People {
age:number;
name:string;
}
// 用接口对变量约束
let child:People = {
age: 18,
name: 'xiaoming'
}
这样就通过接口,完成了对child的类型检查,但是接口的约束是非常严格的,如果数据结构中多一个属性,或者少一个属性,ts都会提示报错,像这样。
let child:People = {
age: 18,
name: 'xiaoming',
// 增加了一个接口中未定义的变量属性
tel: 177734234234
}
不过接口可以添加一个可选属性
interface People {
age:number;
name:string;
// 不必须
tel?: number;
}
这样tel变量就是非必须的,声明时可以省略,类型检查不会报错。
函数接口
interface Add {
// 直接写函数类型
(x:number,y:number):number
}
let add:Add = (a,b) => a + b
在实际编写函数时,函数里的参数名无需和接口里定义名称一样,ts会根据位置进行匹配。
索引类型接口
索引类型就是接口可以描述那些通过索引得到的类型,比如对象中访问属性,people[name], numList[0],可索引类型具有一个 索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。 让我们看一个例子:
ts仅支持两种索引签名:字符串和数字
interface NumList {
// 数字索引,中括号内的类型约束属性名,后面的约束属性值
[index:number] :string
}
let array:NumList = ['a','b']
interface StringList {
// 字符串索引
[x:string] : string
}
let obj:StringList = {a:'a'}
泛型
泛型也是一个前端从没遇见过的概念,上面谈到,我们在声明一些变量的时候需要给这个变量定义好它的类型。
但是有时候,我们可能希望说,实际使用时,这个变量能够有不同的类型,这就需要泛型了。
就像是函数的入参一样,泛型可以理解为变量类型的参数,在实际使用时去传入。
比如定义一个函数
function createValue(val: number):number {
return val + val
}
如果我们希望这个函数可以进行字符串拼接,而不是只能传数值时,则可以用泛型
function createValue<T>(val: T) : T{
return val + val
}
createValue<string>('234')
createValue<number>(234)
使用一个尖括号,来作为类型的入参位置,实际调用时,再传入具体的数据类型。当然也可以不用想这样传入数据类型,因为ts的编译器会根据传入的值,来进行类型推论。
对TS的一点看法
- 首先我不太想把它当做是一门语言,因为它与js的关系,就像是sass、less与css的关系,虽然提供了一些额外的功能,但最终还是要编译成js,因为没有js引擎可以直接运行ts代码的。
- 最开始使用的时候,我甚至觉得它限制了原有js类型的灵活性,比如js中的各种显示和隐形的类型转换,但后续确实啪啪打脸。当我们的代码量庞大后,维护成本增加,有时候为了确定函数的一个入参变量的类型,都得找好久,但使用ts就没有这个问题。
- 当我们提供给其他人使用自己的函数时,可以约束入参的类型,减少问题产生。
ps: 如有错误或补充,欢迎评论交流