文中的TypeScript用TS代替。
一、原始数据类型
原始数据类型包括:布尔值、数值、字符串、null、undefined以及Symbol。
在TS中,定义变量的格式如下所示:let isBoolean: boolean = true即
变量名: 数据类型 = 值。
注意:使用构造函数Boolean创造的对象不是布尔值,返回的是Boolean对象。
let createdByNewBoolean: boolean = new Boolean(1); // Type 'Boolean' is not assignable to type 'boolean'. // 'boolean' is a primitive, but 'Boolean' is a wrapper object. Prefer using 'boolean' when possible.
但是直接调用Boolean(1)可以返回一个boolean类型。
let createdByBoolean: boolean = Boolean(1);
其他基本类型(除了null和undefined)一样。
二、联合类型
联合类型表示取值可以为多种类型中的一种。
let myFavoriteNumber: string | number;
联合类型使用 | 分隔每个类型。
这里的 let myFavoriteNumber: string | number 的含义是,允许 myFavoriteNumber 的类型是 string 或者 number,但是不能是其他类型。
当TS不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法。
三、类型推论
如果没有明确的指定类型,那么TS会依照类型推论的规则自动推断出一个类型。
let myFavoriteNumber = 'seven'; myFavoriteNumber = 7; // index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
第一行代码,TS推断出myFavoriteNumber为字符串类型,就相当于
let myFavoriteNumber: string = 'seven';
第二行赋值就会报错。
注意:如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成any类型而完全不被类型检查。
四、类型断言
类型断言可以用来手动指定一个值的类型。
值 as 类型
-
类型断言的用途:
-
将一个联合类型断言为其中一个类型
有时候,我们需要在不确定类型的时候就访问一个类型特有的属性或方法,比如:
interface Cat { name: string; run(): void; } interface Fish { name: string; swim(): void; } function isFish(animal: Cat | Fish) { if (typeof animal.swim === 'function') { return true; } return false; } // index.ts:11:23 - error TS2339: Property 'swim' does not exist on type 'Cat | Fish'. // Property 'swim' does not exist on type 'Cat'.此时访问
swim会报错,因为访问联合类型的时候,只能访问共有属性和方法。这时候就可以使用类型断言解决该问题,function isFish(animal: Cat | Fish) { if (typeof (animal as Fish).swim === 'function') { return true; } return false; }将
animal断言成Fish。 -
将一个父类断言为更加具体的子类
当类之间有继承关系时,类型断言如下:
class ApiError extends Error { code: number = 0; } class HttpError extends Error { statusCode: number = 200; } function isApiError(error: Error) { if (typeof (error as ApiError).code === 'number') { return true; } return false; }由于
Error中没有code属性,故直接获取error.code会报错,需要使用类型断言获取(error as ApiError).code。 -
将任何一个类型断言为
any比如,有时候我们需要访问一个对象上的属性,例如:
window.foo = 1;我们需要将
window对象上添加一个属性foo,但TS编译时会报错,提示我们window上不存在foo属性。因此我们可以使用
as any临时将window断言为any类型。(window as any).foo = 1;在
any类型的变量上,访问任何属性都是允许的。需要注意的是,将一个变量断言为
any可以说是解决 TypeScript 中类型问题的最后一个手段。 -
将
any断言为一个具体的类型遇到
any类型的变量时,我们可以将它断言成精确的类型。比如getCacheData返回any类型:function getCacheData(key: string): any { return (window as any).cache[key]; }但是,我们可以将它返回值断言成精确类型,提高代码的可维护性,如下:
function getCacheData(key: string): any { return (window as any).cache[key]; } interface Cat { name: string; run(): void; } const tom = getCacheData('tom') as Cat; tom.run();
-
-
类型断言的限制:
并不是任何一个类型都可以被断言为任何另一个类型。
具体来说,若
A兼容B,那么A能够被断言为B,B也能被断言为A。A兼容B,换句话说就是B中含有A的所有属性,可以说是B是父类,A是子类。 -
类型断言 VS 类型声明
看下面两段代码
interface Animal { name: string; } interface Cat { name: string; run(): void; } const animal: Animal = { name: 'tom' }; let tom = animal as Cat;上面是类型断言,再看下面的类型声明
interface Animal { name: string; } interface Cat { name: string; run(): void; } const animal: Animal = { name: 'tom' }; let tom: Cat = animal; // index.ts:12:5 - error TS2741: Property 'run' is missing in type 'Animal' but required in type 'Cat'.会报错,这也很容易理解,可以把
Animal看做是父类,父类的实例当然不能赋值给子类了。所以,两者的区别主要在于:
-
animal断言为Cat,只需要满足Animal兼容Cat或Cat兼容Animal即可 -
animal赋值给tom,需要满足Cat兼容Animal才行
-
-
类型断言 VS 类型转换
类型断言只会影响
TS编译时的类型,类型断言语句会在编译结果中被删除,所以类型断言不是类型转换,它不会真的影响到变量的类型。
五、接口
在TS中,我们使用接口定义对象的类型。
interface Person { name: string; age: number; }
定义的变量比接口少了一些属性、多一些属性是不允许的:
let tom: Person = { name: 'Tom' }; // index.ts(6,5): error TS2322: Type '{ name: string; }' is not assignable to type 'Person'. // Property 'age' is missing in type '{ name: string; }'. let tom: Person = { name: 'Tom', age: 25, gender: 'male' }; // index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'. // Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.
-
可选属性
可选属性可以用
?来表示,含义是该属性可以不存在。age?: number; // 表示定义变量中,age属性可以有可以没有 -
任意属性
使用
[propName: string]: any定义了任意属性取string类型的值。一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:
interface Person { name: string; age?: number; [propName: string]: string; } let tom: Person = { name: 'Tom', age: 25, gender: 'male' }; // index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'. // index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'. // Index signatures are incompatible. // Type 'string | number' is not assignable to type 'string'. // Type 'number' is not assignable to type 'string'.任意属性的值允许是
string,但是可选属性age的值却是number,number不是string的子属性,所以报错了。一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型。
-
只读类型
readonly定义只读类型。 注意,只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候: 若第一次给对象赋值,没有给只读属性赋值,第二次再给只读属性赋值就会报错。
六、数组和元组
1、数组
-
【类型+方括号】表示法
let fibonacci: number[] = [1, 1, 2, 3, 5];数组项中不允许出现其他的类型
-
数组泛型
let fibonacci: Array<number> = [1, 1, 2, 3, 5]; -
接口表示数组
接口也可以用来描述数组:
interface NumberArray { [index: number]: number; } let fibonacci: NumberArray = [1, 1, 2, 3, 5];NumberArray表示:只要索引的类型是数字时,那么值的类型必须是数字。 -
any在数组中的应用一个比较常见的做法是,用
any表示数组中允许出现任意类型:let list: any[] = ['xcatliu', 25, { website: 'http://xcatliu.com' }];
2、元组
数组合并了相同类型的对象,而元组合并了不同类型的对象。
定义一对值分别为string和number的元组
let tom: [string, number] = ['Tom', 25];
当添加越界的元素时,它的类型会被限制为元组中每个类型的联合类型。
七、枚举类型
枚举使用 enum 关键字来定义:
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
枚举成员会被赋值为从0开始递增的数字。
我们也可以给枚举项手动赋值:
enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat};
未手动赋值的项会接着上一个项递增。
枚举项有两种类型:常数项和计算所得项。
-
常数项:上面的例子。
-
计算所得项:
enum Color {Red, Green, Blue = "blue".length};“blue”.length就是计算所得项。但是如果紧接在计算所得项的后面是为手动赋值的项,那么它就会因为无法获得初始值而报错。