除了为变量注解类型之外,类型还可用于其它地方。类型相关的内容由两部分构成:类型定义、类型的使用场景。
原始类型
原始类型有三种:字符串型 string,数字型 number,布尔型 boolean。相应的,有三种内置的包装对象类型:String、Number、Boolean。
数组
数组类型有两种写法:T[]、Array<T>;其中,T 是数组元素的类型。
any
对于类型为 any 的值,允许对它进行任何合乎语法的操作,如:访问任意属性(这些属性也是 any 类型)、作为函数调用、和任何其它类型的变量互相赋值,等等。
let obj: any;
obj.p; // any
obj();
obj = 1;
let n: number = obj;
如果不想让某个值在类型检测时抛出错误,可以使用 any。使用 any 可以越过类型检测,它假定用户比 TypeScript 更了解环境。如果用户想让 TypeScript 相信某一行代码是正确的,又不想书写很长的类型,可以使用 any。
变量的类型注解(annotation)
TypeScript 中声明变量时,可以显式地指明变量类型,也就是类型注解。
let num: number = 1;
实际上,TypeScript 会自动对代码进行类型推断,比如:基于变量初始值推断类型。
函数
TypeScript 允许为函数的参数和返回值注解类型。
参数类型注解
声明函数时可以为每个参数注解类型;即使没有注解参数类型,TypeScript 也会检查参数的数目。
function test(n) {
console.log(n);
}
test(1, 2); // error TS2554: Expected 1 arguments, but got 2.
返回值类型注解
紧接着参数列表的后面,可以为返回值注解类型;实际上,即使不添加返回值类型,TypeScript 也会根据 return 语句进行类型推断;但是,显式添加返回值类型,可在一定程度上避免代码的偶然变动。
匿名函数
使用匿名函数时,TypeScript 会根据所处的上下文来确定函数参数的类型,这被称为上下文类型推断(contextual typing)。
const names = ["tom", "Jerry", "Karl Max"];
names.forEach((name) => {
// (parameter) name: string
console.log(name.toUpperCase());
});
对象类型
定义对象类型只需列出属性以及相应的类型即可,属性之间使用 ',' 或 ';' 隔开,最后一个属性后面的分隔符可选;每个属性的类型部分都是可选的,如果没有指明,则默认为 any;要设置可选属性,只需在属性名和 ':' 之间加 '?' 即可,对于可选属性,使用时一般要检查是否为 undefined。
function test(pt: { x: number; y; z?: string }) {}
// (parameter) pt: {
// x: number;
// y: any;
// z?: string;
// }
联合类型
TypeScript 类型系统提供了各种各样的操作符,用于将已有的类型组合起来以构造新类型,联合类型就是其中之一。
联合类型由两个或更多的类型组合而成,表示这些类型中的某一个,这些类型被称为联合类型的成员,如:number | string。
对于那些类型为联合类型的值,只有联合类型中每个成员都允许执行的操作,才允许执行。如果在代码中对联合类型收窄(narrowing),TypeScript 可以根据代码结构推断出更确切的类型,比如:typeof 运算符、以及 Array.isArray 等类似的判断类型的函数。
注意:
使用了联合符 '
|' 不一定就是联合类型,如果参与联合的类型之间存在包含关系,最终的结果可能不是联合类型,如:type union = 1 | number; // type union = number此外,
boolean本身就是联合类型true | false,虽然boolean中没有包含联合符 '|'。
类型别名
任何类型都可以起一个别名,方便重复使用。
type Point = {
x: number;
y: number;
};
type id = number | string;
别名只是为某一类型起了另一个名字,而不是为同一类型创建不同“版本”。
type otherString = string;
let s: otherString;
s = "1"; // let s: string
接口
接口声明是命名对象类型的一种方式。TypeScript 只关心类型的结构和功能(即:所期待的属性),因而被称为结构化类型系统。
接口与 type 非常相似,都可以表示对象类型,也都可以实现继承,大多情况下可以互相转换。
interface AnimalInterface {
name: string;
}
interface BearInterface extends AnimalInterface {
honey: boolean;
}
let bInterface: BearInterface = {
name: "a",
honey: true,
};
type AnimalType = {
name: string;
};
type BearType = AnimalType & {
honey: boolean;
};
let bType: BearType = {
name: "b",
honey: false,
};
两者间最主要的区别在于:
type无法合并声明,但接口可以;- 接口仅用于声明对象的形状,但无法对类型重命名。
interface WindowInterface {
title: string;
}
interface WindowInterface {
id: number;
}
let s: WindowInterface = {
title: "t",
id: 1,
};
type WindowType = {
// error TS2300: Duplicate identifier 'WindowType'.
title: string;
};
type WindowType = {
// error TS2300: Duplicate identifier 'WindowType'.
id: number;
};
类型断言
对于某个值的类型,用户可能比 TypeScript 知道的更多。此时,可以使用类型断言来为该值指定更确切的类型。类型断言最终会被编译器移除,而不会影响代码的运行时行为。类型断言有两种语法:as、<Type>,其中 Type 为数据类型。
let b: number | string;
const a = b as number; // const a: number
const c = <number>b; // const c: number
对于类型断言,TypeScript 只允许在有交集的类型间互相转化。这种限制可能会让某些运行时正确的类型转换在 TypeScript 编译时报错,此时可使用两次断言来实现类型转换:先转为 any/unknown(先放大),再转为想要的类型(后缩小)。
let a: number | boolean;
let b: string | boolean;
a = b as number | boolean;
let num: number;
let bool: boolean;
num = bool as number; // error TS2352: Conversion of type 'boolean' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
num = bool as unknown as number;
字面量类型
给定的字符串、数字、布尔值也可以作为类型来使用,这被称为字面量类型。
JavaScript 声明变量有三种方式:var、let、const。var、let 声明的是变量,const 声明的是常量。TypeScript 中与 const 相对应的就是字面量类型。声明了字面量类型的变量只能取值为该字面量,const 关键字声明的变量自动被推断为字面量类型。
const constStr = "hello"; // const constStr: "hello"
let letStr = "hello"; // let letStr: string
字面量类型一般与联合类型结合起来,用于描述由一组特定值所构成的类型;字面量类型也可以和非字面量类型一起构成联合类型。
类型
boolean就是字面量联合类型true | false的别名。
对象字面量推断
不管用什么关键字声明对象,都会隐式推断出相应的对象类型;但是,即使 const 声明,推断出的属性也都是可读可写的,因而被推断为较为宽松的类型,如原始数据类型,而不是字面量类型。这在对象属性作为参数传给较窄的类型时,可能会出现问题:
function handleReq(url, method: "get") {}
const req = { url: "www.", method: "get" };
// const req: {
// url: string;
// method: string;
// }
handleReq(req.url, req.method); // error TS2345: Argument of type 'string' is not assignable to parameter of type '"get"'.
下面两种方案可以解决这一问题:
-
断言属性类型
(1)声明时断言,更改属性类型
const req = { url: "www.", method: "get" as "get" }; // const req: { // url: string; // method: "get"; // } handleReq(req.url, req.method);(2)传参时断言
const req = { url: "www.", method: "get" }; handleReq(req.url, req.method as "get"); -
as const断言对象类型此时,对象属性均为只读字面量类型。
const req = { url: "www.", method: "get" } as const; // const req: { // readonly url: "www."; // readonly method: "get"; // } handleReq(req.url, req.method); req.method = "get"; // error TS2540: Cannot assign to 'method' because it is a read-only property.as const是深度断言,即,深层嵌套的属性也会被断言为只读字面量类型。const obj = { a: { a1: "a1", a2: { a21: "a21", }, }, b: 2, } as const; // const obj: { // readonly a: { // readonly a1: "a1"; // readonly a2: { // readonly a21: "a21"; // }; // }; // readonly b: 2; // }
null 和 undefined
TypeScript 为 null 和 undefined 提供了两种与自身同名的类型,标志 strictNullChecks 会影响这两种类型的行为。
strictNullChecks 关闭
null 和 undefined 是其它任何类型的子类型。
let n: number; // let n: number
n = null;
n = undefined;
// No error
无论使用哪个关键字声明,初始值为 null/undefined 的变量都会被隐式推断为 any 类型。
const a = undefined; // const a: any
const b = null; // const b: any
strictNullChecks 打开
类型 null 和 undefined 将与其它原始类型同级,不再有父子类型关系。
let n: number;
n = null; // error TS2322: Type 'null' is not assignable to type 'number'.
n = undefined; // error TS2322: Type 'undefined' is not assignable to type 'number'.
undefined 总是可以赋值给 void 类型,可能是因为 JavaScript 中函数无返回值时默认返回 undefined。
let v: void;
v = undefined;
// No error
无论使用哪个关键字声明,初始值为 null/undefined 的变量都会隐式推断为类型 null/undefined。
var a = undefined; // var a: undefined
let b = null; // let b: null
非 null 断言操作符:后缀 !
紧接着表达式后面书写 !,就是在断言该表达式的值既不是 null,也不是 undefined。
function test(x?: number | null) {
x!.toFixed();
// No error
}
枚举类型 Enum
枚举类型不仅存在于类型层,同时也会影响运行时的行为。
不太常用的原始类型
bigint
ES2020 之后,JavaScript 新增了一种原始数据类型 bigInt,用来表示大整数。
symbol
JavaScript 中 symbol 类型的值通过 Symbol() 函数进行创建,每个值都是全局唯一的。