2. object
在JavaScript中,object是引用类型,它存储的是值的引用。在TypeScript中,当想让一个变量或者函数的参数的类型是一个对象的形式时,可以使用这个类型:
let obj: object obj = { name: 'TypeScript' } obj = 123 // error 不能将类型“123”分配给类型“object” console.log(obj.name) // error 类型“object”上不存在属性“name” 复制代码
可以看到,当给一个对象类型的变量赋值一个对象时,就会报错。对象类型更适合以下场景:
function getKeys (obj: object) { return Object.keys(obj) // 会以列表的形式返回obj中的值 } getKeys({ a: 'a' }) // ['a'] getKeys(123) // error 类型“123”的参数不能赋给类型“object”的参数 复制代码
3. 元组
在 JavaScript 中并没有元组的概念,作为一门动态类型语言,它的优势是支持多类型元素数组。但是出于较好的扩展性、可读性和稳定性考虑,我们通常会把不同类型的值通过键值对的形式塞到一个对象中,再返回这个对象,而不是使用没有任何限制的数组。TypeScript 的元组类型正好弥补了这个不足,使得定义包含固定个数元素、每个元素类型未必相同的数组成为可能。
元组可以看做是数组的扩展,它表示已知元素数量和类型的数组,它特别适合用来实现多值返回。确切的说,就是已知数组中每一个位置上的元素的类型,可以通过元组的索引为元素赋值::
let arr: [string, number, boolean]; arr = ["a", 2, false]; // success arr = [2, "a", false]; // error 不能将类型“number”分配给类型“string”。 不能将类型“string”分配给类型“number”。 arr = ["a", 2]; // error Property '2' is missing in type '[string, number]' but required in type '[string, number, boolean]' arr[1] = 996 复制代码
可以看到,定义的arr元组中,元素个数和元素类型都是确定的,当为arr赋值时,各个位置上的元素类型都要对应,元素个数也要一致。
当访问元组元素时,TypeScript也会对元素做类型检查,如果元素是一个字符串,那么它只能使用字符串方法,如果使用别的类型的方法,就会报错。
在TypeScript 新的版本中,TypeScript会对元组做越界判断。超出规定个数的元素称作越界元素,元素赋值必须类型和个数都对应,不能超出定义的元素个数。
在新的版本中,[string, number]元组类型的声明效果上可以看做等同于下面的声明:
interface Tuple extends Array<number | string> { 0: string; 1: number; length: 2; } 复制代码
这里定义了接口 Tuple ,它继承数组类型,并且数组元素的类型是 number 和 string 构成的联合类型,这样接口 Tuple 就拥有了数组类型所有的特性。并且指定索引为0的值为 string 类型,索引为1的值为 number 类型,同时指定 length 属性的类型字面量为 2,这样在指定一个类型为这个接口 Tuple 时,这个值必须是数组,而且如果元素个数超过2个时,它的length就不是2是大于2的数了,就不满足这个接口定义了,所以就会报错;当然,如果元素个数不够2个也会报错,因为索引为0或1的值缺失。
4. 枚举
TypeScript 在 ES 原有类型基础上加入枚举类型,使得在 TypeScript 中也可以给一组数值赋予名字,这样对开发者比较友好。枚举类型使用enum来定义:
enum Roles { SUPER_ADMIN, ADMIN, USER } 复制代码
上面定义的枚举类型的Roles,它有三个值,TypeScript会为它们每个值分配编号,默认从0开始,在使用时,就可以使用名字而不需要记数字和名称的对应关系了:
enum Roles { SUPER_ADMIN = 0, ADMIN = 1, USER = 2 } const superAdmin = Roles.SUPER_ADMIN; console.log(superAdmin); // 0 console.log(Roles[1]) // ADMIN 复制代码
除此之外,还可以修改这个数值,让SUPER_ADMIN = 1,这样后面的值就分别是2和3。当然还可以给每个值赋予不同的、不按顺序排列的值:
enum Roles { SUPER_ADMIN = 1, ADMIN = 3, USER = 7 } 复制代码
5. any
在编写代码时,有时并不清楚一个值是什么类型,这时就需要用到any类型,它是一个任意类型,定义为any类型的变量就会绕过TypeScript的静态类型检测。对于声明为any类型的值,可以对其进行任何操作,包括获取事实上并不存在的属性、方法,并且 TypeScript 无法检测其属性是否存在、类型是否正确。
我们可以将一个值定义为any类型,也可以在定义数组类型时使用any来指定数组中的元素类型为任意类型:
let value: any; value = 123; value = "abc"; value = false; const array: any[] = [1, "a", true]; 复制代码
any 类型会在对象的调用链中进行传导,即any 类型对象的任意属性的类型都是 any,如下代码所示:
let obj: any = {}; let z = obj.x.y.z; // z的类型是any,不会报错 z(); // success 复制代码
需要注意:不要滥用any类型,如果代码中充满了any,那TypeScript和JavaScript就毫无区别了,所以除非有充足的理由,否则应该尽量避免使用 any ,并且开启禁用隐式 any 的设置。
6. void
void 和 any 相反,any 是表示任意类型,而 void 是表示没有类型,就是什么类型都不是。这在定义函数,并且函数没有返回值时会用到:
const consoleText = (text: string): void => { console.log(text); }; 复制代码
需要注意:void 类型的变量只能赋值为 undefined 和 null ,其他类型不能赋值给 void 类型的变量。
7. never
never 类型指永远不存在值的类型,它是那些总会抛出异常或根本不会有返回值的函数表达式的返回值类型,当变量被永不为真的类型保护所约束时,该变量也是 never 类型。
下面的函数,总是会抛出异常,所以它的返回值类型是never,用来表明它的返回值是不存在的:
const errorFunc = (message: string): never => { throw new Error(message); }; 复制代码
never 类型是任何类型的子类型,所以它可以赋值给任何类型;而没有类型是 never 的子类型,所以除了它自身以外,其他类型(包括 any 类型)都不能为 never 类型赋值。
let neverVariable = (() => { while (true) {} })(); neverVariable = 123; // error 不能将类型"number"分配给类型"never" 复制代码
上面代码定义了一个立即执行函数,函数体是一个死循环,这个函数调用后的返回值类型为 never,所以赋值之后 neverVariable 的类型是 never 类型,当给neverVariable 赋值 123 时,就会报错,因为除它自身外任何类型都不能赋值给 never 类型。
基于 never 的特性,我们可以把 never 作为接口类型下的属性类型,用来禁止操作接口下特定的属性:
const props: { id: number, name?: never } = { id: 1 } props.name = null; // error props.name = 'str'; // error props.name = 1; // error 复制代码
可以看到,无论给 props.name 赋什么类型的值,它都会提示类型错误,这就相当于将 name 属性设置为了只读 。
8. unknown
unknown 是TypeScript在3.0版本新增的类型,主要用来描述类型并不确定的变量。它看起来和any很像,但是还是有区别的,unknown相对于any更安全。
对于any,来看一个例子:
let value: any console.log(value.name) console.log(value.toFixed()) console.log(value.length) 复制代码
上面这些语句都不会报错,因为value是any类型,所以后面三个操作都有合法的情况,当value是一个对象时,访问name属性是没问题的;当value是数值类型的时候,调用它的toFixed方法没问题;当value是字符串或数组时获取它的length属性是没问题的。
当指定值为unknown类型的时候,如果没有缩小类型范围的话,是不能对它进行任何操作的。总之,unknown类型的值不能随便操作。那什么是类型范围缩小呢?下面来看一个例子:
function getValue(value: unknown): string { if (value instanceof Date) { return value.toISOString(); } return String(value); } 复制代码
这里由于把value的类型缩小为Date实例的范围内,所以进行了value.toISOString(),也就是使用ISO标准将 Date 对象转换为字符串。
使用以下方式也可以缩小类型范围:
let result: unknown; if (typeof result === 'number') { result.toFixed(); } 复制代码
关于 unknown 类型,在使用时需要注意以下几点:
- 任何类型的值都可以赋值给 unknown 类型:
let value1: unknown; value1 = "a"; value1 = 123; 复制代码
- unknown 不可以赋值给其它类型,只能赋值给 unknown 和 any 类型:
let value2: unknown; let value3: string = value2; // error 不能将类型“unknown”分配给类型“string” value1 = value2; 复制代码
- unknown 类型的值不能进行任何操作:
let value4: unknown; value4 += 1; // error 对象的类型为 "unknown" 复制代码
- 只能对 unknown 进行等或不等操作,不能进行其它操作:
value1 === value2; value1 !== value2; value1 += value2; // error 复制代码
- unknown 类型的值不能访问其属性、作为函数调用和作为类创建实例:
let value5: unknown; value5.age; // error value5(); // error new value5(); // error 复制代码
在实际使用中,如果有类型无法确定的情况,要尽量避免使用 any,因为 any 会丢失类型信息,一旦一个类型被指定为 any,那么在它上面进行任何操作都是合法的,所以会有意想不到的情况发生。因此如果遇到无法确定类型的情况,要先考虑使用 unknown。