1. 元组元组可以看做是数组的拓展,它表示已知元素数量和类型的数组。确切地说,是已知数组中每一个位置上的元素的类型,来看例子: let tuple : [ string , number , boolean ]; tuple = [ "a" , 2 , false ]; tuple = [ 2 , "a" , false ]; // error 不能将类型“number”分配给类型“string”。 不能将类型“string”分配给类型“number”。 tuple = [ "a" , 2 ]; // error Property '2' is missing in type '[string, number]' but required in type '[string, number, boolean]' tuple [ 1 ] = 3 ; tuple [ 0 ]. split ; // right 类型"string"拥有属性"split" tuple [ 1 ]. split ; // error 类型“number”上不存在属性“split”
可以看到,上面我们定义了一个元组 tuple,它包含三个元素,且每个元素的类型是固定的。当我们为 tuple 赋值时:各个位置上的元素类型都要对应,元素个数也要一致。我们还可以给单个元素赋值:
这里我们给元组 tuple 的索引为 1 即第二个元素赋值为 3,第二个元素类型为 number,我们赋值给 3,所以没有问题。
当我们访问元组中元素时,Typescript 会对我们在元素上做的操作进行检查:
上面的例子中,我们访问的 tuple 的第二个元素的元素类型为 number,而数值没有 split 方法,所以会报错。
在 2.6 版本之前,Typescript 对于元组长度的校验和 2.6 之后的版本有所不同,我们来看下面的例子,前后版本对于该情况的处理: let tuple : [ string , number ]; tuple = [ "a" , 2 ]; // right 类型和个数都对应,没问题 // 2.6版本之前如下也不会报错 tuple = [ "a" , 2 , "b" ]; // 2.6版本之后如下会报错 tuple = [ "a" , 2 , "b" ]; // error 不能将类型“[string, number, string]”分配给类型“[string, number]”。 属性“length”的类型不兼容。
这个赋给元组的值有三个元素,是比我们定义的元组类型元素个数多的:
- 在 2.6 及之前版本中,超出规定个数的元素称作越界元素,但是只要越界元素的类型是定义的类型中的一种即可。
比如我们定义的类型有两种:string 和 number,越界的元素是 string 类型,属于联合类型
string | number
,所以没问题,联合类型的概念我们后面会讲到。 - 在 2.6 之后的版本,去掉了这个越界元素是联合类型的子类型即可的条件,要求元组赋值必须类型和个数都对应。
interface
Tuple
extends
Array
<
number
|
string
>
{
0
:
string
;
1
:
number
;
length
:
2
;
}
上面这个声明中,我们定义接口Tuple
,它继承数组类型,并且数组元素的类型是 number 和 string
构成的联合类型,这样接口Tuple
就拥有了数组类型所有的特性。并且我们明确指定索引为0的值为 interface Tuple extends Array < number | string > { 0 : string ; 1 : number ; }string
类型,索引为1的值为number
类型,同时我们指定 length
属性的类型字面量为 2,这样当我们再指定一个类型为这个接口Tuple
的时候,这个值必须是数组,而且如果元素个数超过2个时,它的length就不是2是大于2的数了,就不满足这个接口定义了,所以就会报错;当然,如果元素个数不够2个也会报错,因为索引为0或1的值缺失。接口我们后面会在后面专门的一节来讲,所以暂时不懂也没关系。如果你想要和 2.6 及之前版本一样的元组特性,那可以这样定义接口:
也就是去掉接口中定义的length: 2
,这样Tuple
接口的length
就是从Array
继承过来的number
类型,而不用必须是2
了。
2. 枚举 enum Roles { SUPER_ADMIN , ADMIN , USER } enum Roles { SUPER_ADMIN = 0 , ADMIN = 1 , USER = 2 } const superAdmin = Roles . SUPER_ADMIN ; console . log ; // 0 enum Roles { SUPER_ADMIN = 1 , ADMIN , USER } enum Roles { SUPER_ADMIN = 1 , ADMIN = 3 , USER = 7 } console . log ; // 'ADMIN'enum
类型在 C++这些语言中比较常见,Typescript 在 ES 原有类型基础上加入枚举类型,使我们在 Typescript 中也可以给一组数值赋予名字,这样对开发者来说较为友好。比如我们要定义一组角色,每一个角色用一个数字代表,就可以使用枚举类型来定义:
上面定义的枚举类型 Roles 里面有三个值,Typescript 会为它们每个值分配编号,默认从 0 开始,依次排列,所以它们对应的值是:
当我们使用的时候,就可以使用名字而不需要记数字和名称的对照关系了:
你也可以修改这个数值,比如你想让这个编码从 1 开始而不是 0,可以如下定义:
这样当你访问Roles.ADMIN
时,它的值就是 2 了。你也可以为每个值都赋予不同的、不按顺序排列的值:
通过名字 Roles.SUPER_ADMIN 可以获取到它对应的值 1,同时你也可以通过值获取到它的名字,以上面任意数值这个例子为前提:
关于枚举更详细的内容可参考
我们来看例子: let value : any ; value = 123 ; value = "abc" ; value = false ; const array : any [] = [ 1 , "a" , true ]; const consoleText = : void => { console . log ; };
你可以看到,我们定义变量 value,指定它的类型为 any,接下来赋予任何类型的值都是可以的。我们还可以在定义数组类型时使用 any 来指定数组中的元素类型为任意类型:
但是请注意,不要滥用 any,如果任何值都指定为 any 类型,那么 Typescript 将失去它的意义。所以如果类型是未知的,更安全的做法是使用unknown类型。4. voidvoid 和 any 相反,any 是表示任意类型,而 void 是表示没有任意类型,就是什么类型都不是,这在我们定义函数,函数没有返回值时会用到:
这个函数没有返回任何的值,所以它的返回类型为 void。
void 类型的变量只能赋值为 const errorFunc = : never => { throw new Error ; }; const infiniteFunc = : never => { while {} };undefined
和 null
,其他类型不能赋值给 void 类型的变量。5. nevernever 类型指那些永不存在的值的类型,它是那些总会抛出异常或根本不会有返回值的函数表达式的返回值类型,当变量被永不为真的类型保护所约束时,该变量也是 never 类型。这个类型比较难理解,我们先来看几个例子:
这个 errorFunc 函数总是会抛出异常,所以它的返回值类型时 never,用来表明它的返回值是永不存在的。infiniteFunc
也是根本不会有返回值的函数,它和之前讲 void 类型时的consoleText
函数不同,consoleText
函数没有返回值,是我们在定义函数的时候没有给它返回值,而infiniteFunc
是死循环是根本不会返回值的,所以它们二者还是有区别的。
never 类型是任何类型的子类型,所以它可以赋值给任何类型;而没有类型是 never 的子类型,所以除了它自身没有任何类型可以赋值给 never 类型,any 类型也不能赋值给 never 类型。来看例子: let neverVariable = => { while {} }); neverVariable = 123 ; // error 不能将类型"number"分配给类型"never"
上面例子我们定义了一个立即执行函数,也就是"let neverVariable = "
右边的内容。右边的函数体内是一个死循环,所以这个函数调用后的返回值类型为 never,所以赋值之后 neverVariable 的类型是 never 类型,当我们给 neverVariable 赋值 123 时,就会报错,因为除它自身外任何类型都不能赋值给 never 类型。
6. unknown let value : any console . log console . log ) console . logunknown
类型是Typescript在3.0版本新增的类型,它表示未知的类型,这样看来它貌似和any很像,但是还是有区别的,也就是所谓的“unknown相对于any是安全的”。怎么理解呢?我们知道当一个值我们不能确定它的类型的时候,可以指定它是any类型;但是当指定了any类型之后,这个值基本上是“废”了,你可以随意对它进行属性方法的访问,不管有的还是没有的,可以把它当做任意类型的值来使用,这往往会产生问题,如下:
上面这些语句都不会报错,因为value是any类型,所以后面三个操作都有合法的情况,当value是一个对象时,访问name属性是没问题的;当value是数值类型的时候,调用它的toFixed方法没问题;当value是字符串或数组时获取它的length属性是没问题的。而当你指定值为unknown类型的时候,如果没有通过基于控制流的类型断言来缩小范围的话,是不能对它进行任何操作的,关于类型断言,我们后面小节会讲到。
总之这里你知道了,unknown类型的值不是可以随便操作的。这里只是先来了解unknown和any的区别,unknown还有很多复杂的规则,待补充。联合类型和交叉类型这要讲的不是Typescript中新增的基本类型,而是高级类型中的两个比较常用类型:联合类型和交叉类型。我们之所以要提前讲解,是因为它俩比较简单,而且很是常用,所以这里提一下。
交叉类型交叉类型就是取多个类型的并集,使用 const merge = < T , U > : T & U => { let res = < T & U > {}; // 这里指定返回值的类型兼备T和U两个类型变量代表的类型的特点 res = Object . assign ; return res ; }; const info1 = { name : "liao" }; const info2 = { age : 18 }; const liaoInfo = merge ; console . log ; // error 类型“{ name: string; } & { age: number; }”上不存在属性“address”&
符号定义,被&符链接的多个类型构成一个交叉类型,表示这个类型同时具备这几个连接起来的类型的特点,来看例子:
补充阅读:Object.assign方法可以合并多个对象,将多个对象的属性添加到一个对象中并返回,有一点要注意的是,如果属性值是对象或者数组这种保存的是内存引用的引用类型,会保持这个引用,也就是如果在Object.assign返回的的对象中修改某个对象属性值,原来用来合并的对象也会受到影响。可以看到,传入的两个参数分别是带有属性 name 和 age 的两个对象,所以它俩的交叉类型要求返回的对象既有 name 属性又有 age 属性。 联合类型联合类型实际是几个类型的结合,但是和交叉类型不同,联合类型是要求只要符合联合类型中任意一种类型即可,它使用
|
符号定义。当我们的程序具有多样性,元素类型不唯一时,即使用联合类型。 const getLength = : number => { if return content . length ; else return content . toString . length ; }; console . log ); // 3 console . log ); // 3
这里我们指定参数既可以是字符串类型也可以是数值类型,这个getLength函数的定义中,其实还涉及到一个知识点,就是类型保护,就是typeof content === “string”
。小结六个Typescript中新增的数据类型,它们是:元组、枚举、Any、void、never和unknown两个简单的高级类型:联合类型和交叉类型。any类型与never类型和unknown类型相比的区别,简单来说,any和never的概念是对立的,而any和unknown类型相似,但是unknown与any相比是较为安全的类型,它并不允许无条件地随意操作。
联合类型和交叉类型,是各种类型的结合,我们可以使用几乎任何类型,来组成联合类型和交叉类型。