美文网首页
TypeScript 学习纪要

TypeScript 学习纪要

作者: percivals | 来源:发表于2021-05-11 10:26 被阅读0次

    TypeScript

    [toc]

    什么是 TypeScript?

    • TypeScript 是添加了类型系统的 JavaScript,适用于任何规模的项目。
    • TypeScript 是一门静态类型、弱类型(允许隐式类型转换)的语言。
    • TypeScript 是完全兼容 JavaScript 的,它不会修改 JavaScript 运行时的特性。
    • TypeScript 可以编译为 JavaScript,然后运行在浏览器、Node.js 等任何能运行 JavaScript 的环境中。
    • TypeScript 拥有很多编译选项,类型检查的严格程度由你决定。
    • TypeScript 可以和 JavaScript 共存,这意味着 JavaScript 项目能够渐进式的迁移到 TypeScript。
    • TypeScript 增强了编辑器(IDE)的功能,提供了代码补全、接口提示、跳转到定义、代码重构等能力。
    • TypeScript 拥有活跃的社区,大多数常用的第三方库都提供了类型声明。
    • TypeScript 与标准同步发展,符合最新的 ECMAScript 标准(stage 3)。

    安装

    TypeScript 的命令行工具安装方法如下:

    npm install -g typescript
    

    以上命令会在全局环境下安装 tsc 命令,安装完成之后,我们就可以在任何地方执行 tsc 命令了。

    编译一个 TypeScript 文件很简单:

    tsc hello.ts
    

    我们约定使用 TypeScript 编写的文件以 .ts 为后缀,用 TypeScript 编写 React 时,以 .tsx为后缀。

    主流的编辑器都支持 TypeScript,这里我推荐使用 Visual Studio Code

    使用基础

    1. 字符串

      `Hello, my name is ${myName}.`
      

      等同于js的下述语句,其中${expr} 用来在模板字符串中嵌入表达式。

      "Hello, my name is " + myName + ".
      
    2. Any 任意值类型

      允许被赋值为任意类型,在任意值上访问任何属性都是允许的,也允许调用任何方法

      let something;
      something = 'seven';
      something = 7;
      
      something.setName('Tom');
      

      编译不会报错,但是如果逻辑未实现,运行依旧会报错

    3. 类型推论

      TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。

      如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查

    4. 联合类型

      联合类型(Union Types)表示取值可以为多种类型中的一种,联合类型使用 | 分隔每个类型

      let myFavoriteNumber: string | number;
      myFavoriteNumber = 'seven';
      myFavoriteNumber = 7;
      
    5. 对象的类型

      在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。

      interface Person {
          name: string;
          age: number;
      }
      
      let tom: Person = {
          name: 'Tom',
          age: 25
      };
      

      有时我们希望不要完全匹配一个形状,那么可以用可选属性:

      interface Person {
          name: string;
          age?: number;
      }
      
      let tom: Person = {
          name: 'Tom'
      };
      

      有时候我们希望一个接口允许有任意的属性,可以使用如下方式:

      interface Person {
          name: string;
          age?: number;
          [propName: string]: any;
      }
      
      let tom: Person = {
          name: 'Tom',
          gender: 'male'
      };
      

      需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集

      interface Person {
          name: string;
          age?: number;
          [propName: string]: string;
      }
      

      上述这种写法会报错,任意属性的值允许是 string,但是可选属性 age 的值却是 numbernumber 不是 string 的子属性,所以报错了。

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

      interface Person {
          name: string;
          age?: number;
          [propName: string]: string | number;
      }
      
      let tom: Person = {
          name: 'Tom',
          age: 25,
          gender: 'male'
      };
      

      只读属性,有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性,该属性不可再次赋值

      interface Person {
          readonly id: number;
          name: string;
          age?: number;
          [propName: string]: any;
      }
      
      let tom: Person = {
          id: 89757,
          name: 'Tom',
          gender: 'male'
      };
      
    6. 数组

      在 TypeScript 中,数组类型有多种定义方式,比较灵活。

      最简单的方法是使用「类型 + 方括号」来表示数组,数组的项中不允许出现其他的类型:

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

      我们也可以使用数组泛型(Array Generic) Array<elemType> 来表示数组:

      let fibonacci: Array<number> = [1, 1, 2, 3, 5];
      
    7. 函数的类型

      函数声明的类型定义:

      function sum(x: number, y: number): number {
          return x + y;
      }
      

      注意,输入多余的(或者少于要求的)参数,是不被允许的

      通过可选参数,可以实现输入多余或少于要求的参数

      function buildName(firstName: string, lastName?: string) {
          if (lastName) {
              return firstName + ' ' + lastName;
          } else {
              return firstName;
          }
      }
      let tomcat = buildName('Tom', 'Cat');
      let tom = buildName('Tom');
      

      需要注意的是,可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必需参数了

      参数默认值:允许给函数的参数添加默认值,TypeScript 会将添加了默认值的参数识别为可选参数,此时就不受「可选参数必须接在必需参数后面」的限制了:

      function buildName(firstName: string, lastName: string = 'Cat') {
          return firstName + ' ' + lastName;
      }
      let tomcat = buildName('Tom', 'Cat');
      let tom = buildName('Tom');
      

      剩余参数

      ES6 中,可以使用 ...rest 的方式获取函数中的剩余参数(rest 参数),注意,rest 参数只能是最后一个参数:

      function push(array, ...items) {
          items.forEach(function(item) {
              array.push(item);
          });
      }
      
      let a: any[] = [];
      push(a, 1, 2, 3);
      

      重载

      我们可以使用重载定义多个 reverse 的函数类型:

      function reverse(x: number): number;
      function reverse(x: string): string;
      function reverse(x: number | string): number | string {
          if (typeof x === 'number') {
              return Number(x.toString().split('').reverse().join(''));
          } else if (typeof x === 'string') {
              return x.split('').reverse().join('');
          }
      }
      

      上例中,我们重复定义了多次函数 reverse,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。

      注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。

    8. 类型断言

      值 as 类型
      

      类型断言的常见用途有以下几种:

      • 联合类型可以被断言为其中一个类型
      • 父类可以被断言为子类
      • 任何类型都可以被断言为 any
      • any 可以被断言为任何类型
      • 要使得 A 能够被断言为 B,只需要 A 兼容 BB 兼容 A 即可
    9. 声明文件 https://ts.xcatliu.com/basics/declaration-files.html#declare-var

      1)通常我们会把声明语句放到一个单独的文件(jQuery.d.ts)中,这就是声明文件,声明文件必需以 .d.ts 为后缀。

      一般来说,ts 会解析项目中所有的 *.ts 文件,当然也包含以 .d.ts 结尾的文件。所以当我们将 jQuery.d.ts 放到项目中时,其他所有 *.ts 文件就都可以获得 jQuery 的类型定义了。
      
      /path/to/project
      ├── src
      |  ├── index.ts
      |  └── jQuery.d.ts
      └── tsconfig.json
      
      假如仍然无法解析,那么可以检查下 tsconfig.json 中的 files、include 和 exclude 配置,确保其包含了 jQuery.d.ts 文件。
      

      2)第三方声明文件

      当然,jQuery 的声明文件不需要我们定义了,社区已经帮我们定义好了:jQuery in DefinitelyTyped。
      我们可以直接下载下来使用,但是更推荐的是使用 @types 统一管理第三方库的声明文件。
      @types 的使用方式很简单,直接用 npm 安装对应的声明模块即可,以 jQuery 举例:
      npm install @types/jquery --save-dev
      可以在这个页面搜索你需要的声明文件
      

      3)书写声明文件

      当一个第三方库没有提供声明文件时,我们就需要自己书写声明文件了。

    进阶知识

    1. 类型别名

      我们使用 type 创建类型别名,类型别名常用于联合类型

      type Name = string;
      type NameResolver = () => string;
      type NameOrResolver = Name | NameResolver;
      function getName(n: NameOrResolver): Name {
          if (typeof n === 'string') {
              return n;
          } else {
              return n();
          }
      }
      
    2. 字符串字面量类型

      字符串字面量类型用来约束取值只能是某几个字符串中的一个。

      type EventNames = 'click' | 'scroll' | 'mousemove';
      function handleEvent(ele: Element, event: EventNames) {
          // do something
      }
      
      handleEvent(document.getElementById('hello'), 'scroll');  // 没问题
      handleEvent(document.getElementById('world'), 'dblclick'); // 报错,event 不能为 'dblclick'
      
      // index.ts(7,47): error TS2345: Argument of type '"dblclick"' is not assignable to parameter of type 'EventNames'.
      

      上例中,我们使用 type 定了一个字符串字面量类型 EventNames,它只能取三种字符串中的一种。

      注意,类型别名与字符串字面量类型都是使用 type 进行定义。

    3. 元组

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

      元组起源于函数编程语言(如 F#),这些语言中会频繁使用元组。

      let tom: [string, number] = ['Tom', 25];
      

      当添加越界的元素时,它的类型会被限制为元组中每个类型的联合类型:

      let tom: [string, number];
      tom = ['Tom', 25];
      tom.push('male');
      
    4. 枚举

      枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。

      枚举使用 enum 关键字来定义:

      enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
      

      枚举成员会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射:

      enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
      
      console.log(Days["Sun"] === 0); // true
      console.log(Days["Mon"] === 1); // true
      console.log(Days["Tue"] === 2); // true
      console.log(Days["Sat"] === 6); // true
      
      console.log(Days[0] === "Sun"); // true
      console.log(Days[1] === "Mon"); // true
      console.log(Days[2] === "Tue"); // true
      console.log(Days[6] === "Sat"); // true
      

      我们也可以给枚举项手动赋值:

      enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat};
      
      console.log(Days["Sun"] === 7); // true
      console.log(Days["Mon"] === 1); // true
      console.log(Days["Tue"] === 2); // true
      console.log(Days["Sat"] === 6); // true
      
    5. 1)属性和方法

      使用 class 定义类,使用 constructor 定义构造函数

      通过 new 生成新实例的时候,会自动调用构造函数。

      2)类的继承

      使用 extends 关键字实现继承,子类中使用 super 关键字来调用父类的构造函数和方法

      class Cat extends Animal {
        constructor(name) {
          super(name); // 调用父类的 constructor(name)
          console.log(this.name);
        }
        sayHi() {
          return 'Meow, ' + super.sayHi(); // 调用父类的 sayHi()
        }
      }
      
      let c = new Cat('Tom'); // Tom
      console.log(c.sayHi()); // Meow, My name is Tom
      

      3)静态方法

      使用 static 修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用:

      4)TypeScript中类的用法

      TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 public、private 和 protected。
      public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
      private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
      protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的
      
    6. 类与接口

      实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 implements 关键字来实现。这个特性大大提高了面向对象的灵活性。

      interface Alarm {
          alert(): void;
      }
      
      class Door {
      }
      
      class SecurityDoor extends Door implements Alarm {
          alert() {
              console.log('SecurityDoor alert');
          }
      }
      
      class Car implements Alarm {
          alert() {
              console.log('Car alert');
          }
      }
      

      一个类可以实现多个接口:

      interface Alarm {
          alert(): void;
      }
      
      interface Light {
          lightOn(): void;
          lightOff(): void;
      }
      
      class Car implements Alarm, Light {
          alert() {
              console.log('Car alert');
          }
          lightOn() {
              console.log('Car light on');
          }
          lightOff() {
              console.log('Car light off');
          }
      }
      

      接口继承接口

      接口与接口之间可以是继承关系:

      interface Alarm {
          alert(): void;
      }
      
      interface LightableAlarm extends Alarm {
          lightOn(): void;
          lightOff(): void;
      }
      

      这很好理解,LightableAlarm 继承了 Alarm,除了拥有 alert 方法之外,还拥有两个新方法 lightOnlightOff

      接口继承类

      常见的面向对象语言中,接口是不能继承类的,但是在 TypeScript 中却是可以的:

      class Point {
          x: number;
          y: number;
          constructor(x: number, y: number) {
              this.x = x;
              this.y = y;
          }
      }
      
      interface Point3d extends Point {
          z: number;
      }
      
      let point3d: Point3d = {x: 1, y: 2, z: 3};
      

      为什么 TypeScript 会支持接口继承类呢?

      实际上,当我们在声明 class Point 时,除了会创建一个名为 Point 的类之外,同时也创建了一个名为 Point 的类型(实例的类型)。

      所以我们既可以将 Point 当做一个类来用(使用 new Point 创建它的实例)

      也可以将 Point 当做一个类型来用(使用 : Point 表示参数的类型)

      class Point {
          x: number;
          y: number;
          constructor(x: number, y: number) {
              this.x = x;
              this.y = y;
          }
      }
      
      interface PointInstanceType {
          x: number;
          y: number;
      }
      
      function printPoint(p: PointInstanceType) {
          console.log(p.x, p.y);
      }
      
      printPoint(new Point(1, 2));
      

      声明 Point 类时创建的 Point 类型只包含其中的实例属性和实例方法,不包含构造函数、静态属性、静态方法等

    7. 泛型

      泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

      function createArray<T>(length: number, value: T): Array<T> {
          let result: T[] = [];
          for (let i = 0; i < length; i++) {
              result[i] = value;
          }
          return result;
      }
      
      createArray<string>(3, 'x'); // ['x', 'x', 'x']
      

      定义泛型的时候,可以一次定义多个类型参数:

      function swap<T, U>(tuple: [T, U]): [U, T] {
          return [tuple[1], tuple[0]];
      }
      
      swap([7, 'seven']); // ['seven', 7]
      

      泛型约束

      interface Lengthwise {
          length: number;
      }
      
      function loggingIdentity<T extends Lengthwise>(arg: T): T {
          console.log(arg.length);
          return arg;
      }
      

      上例中,我们使用了 extends 约束了泛型 T 必须符合接口 Lengthwise 的形状,也就是必须包含 length 属性。

    参考:https://ts.xcatliu.com/engineering/lint.html

    相关文章

      网友评论

          本文标题:TypeScript 学习纪要

          本文链接:https://www.haomeiwen.com/subject/ndsgdltx.html