前言
重点!重点!重点!这篇文章并不适用于TypeScript熟手,大概只有像我一样的基本没有了解过的人才更适合吧...当然如果大家有什么意见或者文章有什么不对的地方,希望大家指正
在我短暂的前端开发生涯中,一直是在使用JavaScript进行开发,我觉得JavaScript真的非常好用,可以说是我使用过的语言中最好用的语言之一,以至于到后来第一次使用TypeScript的时候有一些抵触,再加上使用过程中的不顺利,让我对TypeScript更是深恶痛绝。
但是为了不落后于人,顺便领略大家口中TS的真香定律,我就计划从头到尾的捋一遍TypeScript,所以就有了这个系列,希望能更新到完吧。
TypeScript简介
TypeScript(简称TS)是微软开发的一个开源的编程语言,通过在JavaScript的基础上添加静态类型定义构建而成。TypeScript通过TypeScript编译器或Babel转译为JavaScript代码,可运行在任何浏览器,任何操作系统
在2012年10月,微软发布了首个公开版本的TypeScript,2013年6月19日,在经历了一个预览版之后微软正式发布了正式版TypeScript。当前最新版本为TypeScript 4.5。
让我们开始正文
数据类型
首先将TS中的数据类型简单分一下类:
基本数据类型: string
、number
、boolean
、undefined
、null
、Symbol
、bigint
引用数据类型: Array
,Tuple
,object
,function
特殊数据类型: any
,unknow
,void
,never
,enum
1.基本数据类型
let num:number = 0
let str:string = 'string'
let bool:boolean = true/false
let symbol:Symbol = Symbol()
let bigI:bigint = 10n
let unde:undefined = undefined
let nul:null = null
2.引用数据类型
2.1 数组Array
//可以使用[],也可以使用Array<?>来限定类型
let numArray:number[] = [4,3,6]
numArray[3] = 'strs' //Error: Type 'string' is not assignable to type 'number'.(2322)
let strArray:Array<string>= ['3','5','3']
strArray[3] = 2 //Error: Type 'number' is not assignable to type 'string'.(2322)
let nos:Array<string|number> = [1,'2',3,'4','5']
nos[5] = false //Error:Type 'boolean' is not assignable to type 'string | number'.(2322)
2.2 元组Tuple
元组允许表示一个已知元素数量和元素类型的数组,赋值时的数量和类型与定义时应该一一对应。
let son:[string,number]
son = ['1',2]
console.log(son[0])
console.log(son[1])
值得注意的是在2.6版本及以前
,[string,number,string]是被认为是[string,number]的子类,
也就是说,我们在确保插入的数据是我们规定好的类型的时候,其实是可以越界的
翻译过来就是: 在TypeScript 2.6 及更早版本中,[number, string, string] 被认为是 [number, string] 的子类型。这是由 TypeScript 的结构性质推动的。 [number, string, string] 的第一个和第二个元素分别是 [number, string] 的第一个和第二个元素的子类型。 引用:TypeScript: Documentation - TypeScript 2.7 (typescriptlang.org). 但是由于在使用过程中与其他大部分场景冲突,所以在2.7版本以后元组就不允许越界了
let son!:[string,number]
son = ['1',2]
console.log(son[0].substring(1))
console.log(son[1].substring(1)) //Error Property 'substring' does not exist on type 'number'.(2339)
-------分割线-------
son = ['1',2,2]
//这一行代码会报错,不允许这样写
Error:Type '[string, number, number]' is not assignable to type '[string, number]'.
Source has 3 element(s) but target allows only 2.(2322)
虽然Tuple可以向其中继续插入数据(不建议这样做), 但是Tuple是不能越界访问的
son.push('3') //这样是OK的
son.push(4) //这样是OK的
son.push(false) //Error:Argument of type 'boolean' is not assignable to parameter of type 'string | number'.(2345)
console.log(son[3]) //Error:Tuple type '[string, number]' of length '2' has no element at index '3'.(2493)
元组的可选元素类型,我们可以在一个类型后面加上?
表明这类型是可选的,这个功能还是挺有用的,不过有一点需要注意,必选元素类型不能放在可选元素类型后面
,也就是说只要元组中出现一个元素类型,那么它后面的所有元素类型都将是可选元素类型
let son!:[string,boolean?,number] //Error: A required element cannot follow an optional element.(1257)
另外如果设置了可选元素,是不能跳过第一个可选类型直接赋值第二个可选类型的。
let son!:[string,boolean?,number?]
son = ['1',2] //Type 'number' is not assignable to type 'boolean | undefined'.(2322)
2.3 函数function
可以通过 function
或者 箭头函数
来定义一个函数。
定义函数之后,可以显示声明返回值类型,一旦声明返回值类型就必须有对应类型的返回值;也可以省略掉声明返回值类型的过程,TS的类型推论会帮助我们推断出返回值类型
function getName(name:string){
console.log(`姓名:${name}`)
return `姓名:${name}`
}
function getName():string{
//声明了要有string类型的返回值,就一定要返回一个string
return 'Alliances'
}
const getDeviceName = () =>{
return 'HUAWEI'
}
2.3.1 参数类型
非必传参数: 我们可以在参数类型后面加上?
表明这个参数是可选的,并不强制传入
const setUserInfo = (name:string,age?:number) =>{
console.log(`姓名${name}; age:${age}`)
}
setUserInfo('Alliances')
setUserInfo('Alliances',20)
[LOG]: "姓名Alliances; age:undefined"
[LOG]: "姓名Alliances; age:20"
PS. 跟Tuple同样的规则,非必传参数应该在所有参数的最后面。
下面这段代码就是错误示例,在age
设定为非必传参数之后,gender
也应该是非必传参数
const setUserInfo = (name:string,age?:number,gender:string) =>{
//Error:A required parameter cannot follow an optional parameter.
console.log(`姓名${name}; age:${age}; gender:${gender}`)
}
setUserInfo('Alliances')
setUserInfo('Alliances',21,'男')
默认参数: 可以通过=
来给传入的参数赋予一个默认值。在开发过程中,给传入的参数赋予一个默认值是非常方便且必要的举措
const setUserInfo = (name:string,age:number = 20) =>{
console.log(`姓名${name}; age:${age}`)
}
setUserInfo('Alliances')
setUserInfo('Alliances',21)
[LOG]: "姓名Alliances; age:20"
[LOG]: "姓名Alliances; age:21"
rest parameter: 挺熟悉的一个东西,...
操作符,曾经有人问我这不就是省略号吗?
const setUserInfo = (...strArray:string) =>{} //Error: A rest parameter must be of an array type
--------分割线--------
const getMul = (...mul: number[]) =>
console.log(`乘积:${mul.reduce((value, item) => (value *= item))}`)
getMul(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// [LOG]: "乘积:3628800"
2.3.2 函数重载
重载: 同一个函数同时拥有多个函数类型定义,TS为了保证类型安全,支持了函数签名的类型重载,即多个overload signatures
和一个implementation signatures
class Computer{
deviceName: string= 'computer';
deviceId:number = 0x001
setDeviceOrId(device:string):void; //1
setDeviceOrId(device: number):void; //2
setDeviceOrId(device: string|number){ //3
if(typeof device === 'string'){
this.deviceName = `设备名称: ${device}`
}else{
this.deviceId = device
}
}
getDevice(){
return this.deviceName + ` ; ID: ${this.deviceId}`
}
}
let computer = new Computer()
computer.setDeviceOrId('Laptop')
computer.setDeviceOrId(0x002)
console.log(computer.getDevice())
//打印结果:[LOG]: "设备名称: Laptop ; ID: 2"
看着挺有用的,TS的重载不像是之前写Java时候的重载, 用的时候还不太适应
2.4 object
object
表示非原始类型,也就是除number
,string
,boolean
,symbol
,null
或undefined
之外的类型。
在初始化的时候直接使用object
也是可以的,但是一旦要修改内部的属性值时就会报错,这是因为你没有明确的定义object
内部的元素类型。
所以在使用object
的时候,最好还是明确定义好内部属性的类型值
let obj:object = {a:1,b:2}
obj.a = 5 //Error Property 'a' does not exist on type 'object'.(2339)
let obj: {a:number,b:number} = {a:1,b:2}
obj.a = 3 //OK
Object
let obj:Object = 2
obj = '3'
obj = false
obj = Symbol()
obj = {}
obj = 10n
obj = undefined //Error: Type 'undefined' is not assignable to type 'Object'.(2322)
obj = null //Erro: Type 'null' is not assignable to type 'Object'.(2322)
3.特殊数据类型
3.1 any
在 TS 中,任何类型都可以归于 any
类型,所以any
类型也就成了所有类型的顶级类型,如果你不指定一个类型的时候,那么它就会默认是any类型。同时不建议使用any,我认为这样会破坏TS的类型优势
let anyObj:any // 等同于 let anyO
anyObj = 1 //OK
anyObj = 'str' //OK
anyObj = false //OK
3.2 unknow
TS在3.0版本引入了unknow
,它是除any
以外的顶级类型。
unknow
比any
更加严格,它可以被分配任何类型的值,但是只能赋值给unknow
或者any
类型,赋值给其他类型都会报错
所有unknow
和any
都属于TS的顶级类型,它们俩有啥区别?unknow
更加严格也更加安全,相比之下any
宽松得多,使用any
会跳过类型检查,这就意味着放弃了TS的类型优势
var unk:unknown
var abc:any = 1
var cde:unknown
var efg = 12
abc = unk
cde = unk
efg = unk //Error- Type 'unknown' is not assignable to type 'number'.
3.3 void
void当一个函数没有任何返回值时候,就会使用void,但是实际上void返回的是undefined类型,例如
const voidTest = () ={
return //ok
return undefined //ok
return 1 //error
}
3.4 never
never
表示永远都不会有返回值,它可以被认为是void
的子类。
即使是void
也可以返回 null
、undefined
,而never
不能返回任何类型,即使是undefined
都不行。
function nevFun():never{
return undefined //Error: Type 'undefined' is not assignable to type 'never'.(2322
}
never
虽然不能返回任何值,但是他可以用在代码阻断上,例如返回一个异常
function nevFun():never{
throw new Error('抛出一个Error')
}
3.5 enum
创建一组带名字的常量,更加清晰直观的表达某一个状态 类型只能是string、number
3.5.1 number类型
enum enumType{
A,
B,
C=9,
D, // 10
E, // 11
}
1、 枚举默认为number
类型,并且默认从0开始累加,如果设置了默认值,那么只会影响之后的值
console.log(enumType.A) //0
console.log(enumType.B) //1
console.log(enumType.E) //11
2、 反向映射(成员名-成员值的映射)
let numA = enumType[0]
console.log(numA) // ‘A’
console.log(enumType[3]) //undefined
console.log(enumType[9]) //‘C’
我们可以看一下enum
经过编译后的JavaScript
代码
// var enumType;
// (function (enumType) {
// enumType[enumType["A"] = 0] = "A";
// enumType[enumType["B"] = 1] = "B";
// enumType[enumType["C"] = 9] = "C";
// enumType[enumType["D"] = 10] = "D";
// enumType[enumType["E"] = 11] = "E";
// })(enumType || (enumType = {}));
这段代码就可以解释反向映射的原理,我们可以拆分一下看看
1、首先声明了一个作用域enumType
,这是我们声明的枚举名
2、然后就是enumType["A"] = 0
, 这一步会把enumType
里面的A
赋值0
,又因为赋值运算会把计算的值返回出去,所以enumType["A"] = 0
得到结果0
3、然后就比较清晰了,根据第二步可以知道 enumType[0] = "A"
,这样就达到一个反向映射的效果
4、我们手动赋予的值也是同样的道理
3.5.2 string类型
必须有默认值且不支持反向映射 !
3.5.3 常量枚举
使用const
修饰enum
,这种类型不会编译成任何 JS,只会编译对应的值
注意,只有常量值才能使用常量枚举,如果赋予计算值的话会报错
const enum enumType{
A = 'ABC',
B = 123,
// C = A.length //Error: Computed values are not permitted in an enum with string valued members.(2553)
D = 'DEF'
}
let ABC = 'ABC'
if(ABC === enumType.A){
console.log('两者相等')
}
上面的代码是一段普通的TS代码,下面就是经过编译后的JavaScript
代码了, 可以看到其中并没有看到常量枚举的存在, 只有我们用过的enumType.A
的值参与判断
let ABC = 'ABC';
if (ABC === "ABC" /* enumType.A */) {
console.log('两者相等');
}
3.5.4 异构枚举
包含string
和number
的混合体,反向映射同理
enum enumType{
A,
B,
C=9,
D, // 10
E, // 11
F = 'ABC'
}
console.log(enumType.A) //0
console.log(enumType.B) //1
console.log(enumType.E) //11
let numA = enumType[0]
console.log(numA) // ‘A’
console.log(enumType[3]) //undefined
console.log(enumType[9]) //‘C’
let strF = enumType.F
console.log(strF) // ‘ABC’
//得到结果如下
[LOG]: 0
[LOG]: 1
[LOG]: 11
[LOG]: "A"
[LOG]: undefined
[LOG]: "C"
[LOG]: "ABC"
3.5.5 字面量类型
目前只支持string|number|boolean
类型,可以指定参数的类型是什么,指定什么类型,那么变量的值只能是指定的值,例如
let string: ‘ABC’ //我们指定string的字面量为ABC
string = ‘ABC’ //OK
string = ‘CDE’ // error
let bool: true
bool = true // OK
bool = false // error
3.5.6 交叉类型
将多个类型合并,使用&
链接
type AProps = {a:string}
type BProps = {b: number}
type AllProps = AProps & BProps
AllProps = {a: string,b:number}
const info:AllProps = {
a:’YXD’,
b:123
}
3.5.7 同名基础属性合并
如果是同样的基础类型,合并之后也会是这个类型。那么同一个名称不同类型合并之后,会是什么类型?
在A和B中同时添加一个名为c的属性,但是两个类型完全不同,那么我们合并AProps和BProps之后,c的类型会是什么?是string
还是number
,又或者两种都可以?
实际上,合并之后c的类型完全不是我们猜想的那样。
在合并之后,c的类型不能是string
,也不能是number
,c的类型应该是never
。
因为在合并之后,c的类型会变成 string & number
,这样的属性是不存在的,从报错来看c的类型应该是never
type AProps = {a:string,c:number}
type BProps = {b: number,c:string}
type AllProps = AProps & BProps
const info:AllProps = {
a:’YXD’,
b:123,
c:456, // error • Type 'number' is not assignable to type 'never'.
c:’132’ // error • Type 'string' is not assignable to type 'never'.
}
3.5.8 同名非基础属性合并
type AProps = {a: string}
type BProps = {b: number}
interface C{
x: AProps
}
interface D{
x: BProps
}
type allProps = C & D
const info:allProps = {
x:{
a: 'YXD',
b: 123
}
}
对于混入多个类型时,若存在相同的成员,且成员类型为非基本数据类型,那么是可以成功合并。
如果 接口A 中的 也是 b,类型为number,就会跟同名基础属性合并一样
结 尾
数据类型这篇到这里算是结束了, 大概捋清楚TS的数据类型,对于我来说收获还是不小的,接下来希望能把TS中的class
、类型守卫
、断言
搞清楚。
如果您发现文章中有任何问题的话,希望可以在评论区指正。
谢谢大家!