TypeScript笔记(上)

190 阅读7分钟

文中的TypeScriptTS代替。

一、原始数据类型

原始数据类型包括:布尔值、数值、字符串、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);

其他基本类型(除了nullundefined)一样。

二、联合类型

联合类型表示取值可以为多种类型中的一种。

 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 类型
  1. 类型断言的用途:

    • 将一个联合类型断言为其中一个类型

      有时候,我们需要在不确定类型的时候就访问一个类型特有的属性或方法,比如:

       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();
      
  2. 类型断言的限制:

    并不是任何一个类型都可以被断言为任何另一个类型。

    具体来说,若A兼容B,那么A能够被断言为BB也能被断言为AA兼容B,换句话说就是B中含有A的所有属性,可以说是B是父类,A是子类。

  3. 类型断言 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 兼容 CatCat 兼容 Animal 即可

    • animal 赋值给 tom,需要满足 Cat 兼容 Animal 才行

  4. 类型断言 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'.
  1. 可选属性

    可选属性可以用?来表示,含义是该属性可以不存在。

     age?: number; // 表示定义变量中,age属性可以有可以没有
    
  2. 任意属性

    使用[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 的值却是 numbernumber 不是 string 的子属性,所以报错了。

    一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型。

  3. 只读类型

    readonly定义只读类型。 注意,只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候: 若第一次给对象赋值,没有给只读属性赋值,第二次再给只读属性赋值就会报错。

六、数组和元组

1、数组
  1. 【类型+方括号】表示法

     let fibonacci: number[] = [1, 1, 2, 3, 5];
    

    数组项中不允许出现其他的类型

  2. 数组泛型

     let fibonacci: Array<number> = [1, 1, 2, 3, 5];
    
  3. 接口表示数组

    接口也可以用来描述数组:

     interface NumberArray {     [index: number]: number; } let fibonacci: NumberArray = [1, 1, 2, 3, 5];
    

    NumberArray 表示:只要索引的类型是数字时,那么值的类型必须是数字。

  4. any在数组中的应用

    一个比较常见的做法是,用 any 表示数组中允许出现任意类型:

     let list: any[] = ['xcatliu', 25, { website: 'http://xcatliu.com' }];
    
2、元组

数组合并了相同类型的对象,而元组合并了不同类型的对象。

定义一对值分别为stringnumber的元组

 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};

未手动赋值的项会接着上一个项递增。

枚举项有两种类型:常数项和计算所得项。

  1. 常数项:上面的例子。

  2. 计算所得项:

     enum Color {Red, Green, Blue = "blue".length};
    

    “blue”.length就是计算所得项。

    但是如果紧接在计算所得项的后面是为手动赋值的项,那么它就会因为无法获得初始值而报错。