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,
复制代码
上面定义的枚举类型的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 类型,在使用时需要注意以下几点:
let value1: unknown;
value1 = "a";
value1 = 123;
复制代码
-
unknown 不可以赋值给其它类型,只能赋值给 unknown 和 any 类型:
let value2: unknown;
let value3: string = value2; // error 不能将类型“unknown”分配给类型“string”
value1 = value2;
复制代码
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。