TypeScript 从混乱到清晰:我的类型系统学习之路

41 阅读6分钟

基础类型

布尔值 boolean、数字 number、字符串 string

let isBoy: boolean = true;
let age: number = 18;
let name: string = "pbstar";

数组 Array

let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];

元组 Tuple 元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。

let infoList: [string, number] = ["pbstar", 18];

枚举 enum 使用枚举我们可以定义一些有名字的数字常量。枚举通过 enum 关键字来定义。

默认情况下,从 0 开始为元素编号。可以手动的指定成员的数值。

enum Color {
  Red = 1,
  Green,
  Blue,
}
let colorName: string = Color[2]; // Green

枚举成员可以是常数或计算得出的值:

enum FileAccess {
  None,
  Read = 1 << 1,
  Write = 1 << 2,
  ReadWrite = Read | Write,
  G = "123".length, // 计算得出的成员
}

枚举在运行时真正存在,支持双向映射(name ↔ value):

enum Enum {
  A,
}
let a = Enum.A; // 0
let nameOfA = Enum[a]; // "A"

symbol 类型 symbol 类型是 ES6 引入的新类型,用于表示唯一的标识符。

let sym1: symbol = Symbol("key");
let sym2: symbol = Symbol("key");
sym1 === sym2; // false

任意值 any,空值 void,Null,Undefined any 可以赋值任意类型,void 只能赋值 undefined 和 null,Null 和 Undefined 类型的值只有它们本身。

无值 Never

类型断言

类型断言有两种形式。 其一是"尖括号"语法,另一个为"as"语法。

let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
let strLength: number = (someValue as string).length;

接口 interface

接口(Interface) 用于定义对象的类型结构,描述了对象应该具有的属性和方法。

基本语法

interface Person {
  name: string;
  age: number;
}

let person: Person = {
  name: "pbstar",
  age: 18,
};

可选属性

在属性名后加 ? 表示该属性是可选的:

interface Person {
  name: string;
  age?: number; // 可选属性
}

let person1: Person = { name: "pbstar" }; // 合法
let person2: Person = { name: "pbstar", age: 18 }; // 合法

只读属性

在属性名前加 readonly 表示该属性是只读的:

interface Person {
  readonly id: number;
  name: string;
}

let person: Person = { id: 1, name: "pbstar" };
person.id = 2; // 错误!id 是只读属性

函数类型

接口可以描述函数类型:

interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc = function (source: string, subString: string) {
  return source.search(subString) > -1;
};

可索引类型

接口可以描述具有索引签名的对象:

interface StringArray {
  [index: number]: string;
}

let myArray: StringArray = ["pbstar", "typescript"];
let myStr: string = myArray[0]; // "pbstar"

接口继承

接口可以相互继承,实现代码复用:

interface Shape {
  color: string;
}

interface Square extends Shape {
  sideLength: number;
}

let square: Square = {
  color: "blue",
  sideLength: 10,
};

混合类型

接口可以描述同时具有多种类型的对象:

interface Counter {
  (start: number): string;
  interval: number;
  reset(): void;
}

function getCounter(): Counter {
  let counter = function (start: number) {
    return String(start);
  } as Counter;
  counter.interval = 123;
  counter.reset = function () {};
  return counter;
}

类 class

类(Class) 是面向对象编程的核心概念,TypeScript 提供了基于类的面向对象编程方式。

基本语法

class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return "Hello, " + this.greeting;
  }
}

let greeter = new Greeter("world");
console.log(greeter.greet()); // "Hello, world"

类的继承

使用 extends 关键字实现类的继承:

class Animal {
  name: string;
  constructor(theName: string) {
    this.name = theName;
  }
  move(distanceInMeters: number = 0) {
    console.log(`${this.name} moved ${distanceInMeters}m.`);
  }
}

class Dog extends Animal {
  bark() {
    console.log("Woof! Woof!");
  }
}

const dog = new Dog("Buddy");
dog.bark(); // "Woof! Woof!"
dog.move(10); // "Buddy moved 10m."

访问修饰符

TypeScript 提供了三种访问修饰符:

public(默认)

成员默认为 public,可以自由访问:

class Animal {
  public name: string;
  public constructor(theName: string) {
    this.name = theName;
  }
  public move(distanceInMeters: number) {
    console.log(`${this.name} moved ${distanceInMeters}m.`);
  }
}

private

私有成员不能在声明它的类的外部访问:

class Animal {
  private name: string;
  constructor(theName: string) {
    this.name = theName;
  }
}

new Animal("Cat").name; // 错误: 'name' 是私有的

protected

受保护成员在派生类中可以访问:

class Person {
  protected name: string;
  constructor(name: string) {
    this.name = name;
  }
}

class Employee extends Person {
  private department: string;
  constructor(name: string, department: string) {
    super(name);
    this.department = department;
  }
  getElevatorPitch() {
    return `Hello, my name is ${this.name} and I work in ${this.department}.`;
  }
}

只读属性

使用 readonly 关键字将属性设置为只读:

class Octopus {
  readonly name: string;
  readonly numberOfLegs: number = 8;
  constructor(theName: string) {
    this.name = theName;
  }
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // 错误! name 是只读的

参数属性

参数属性可以方便地在一个地方创建并初始化成员:

class Octopus {
  readonly numberOfLegs: number = 8;
  constructor(readonly name: string) {
    // 参数属性会创建并初始化 name
  }
}

存取器

使用 getter 和 setter 控制对成员的访问:

class Employee {
  private _fullName: string = "";
  get fullName(): string {
    return this._fullName;
  }
  set fullName(newName: string) {
    if (newName && newName.length > 0) {
      this._fullName = newName;
    }
  }
}

let employee = new Employee();
employee.fullName = "Bob Smith";
console.log(employee.fullName); // "Bob Smith"

静态属性

使用 static 关键字定义类的静态成员:

class Grid {
  static origin = { x: 0, y: 0 };
  calculateDistanceFromOrigin(point: { x: number; y: number }) {
    let xDist = point.x - Grid.origin.x;
    let yDist = point.y - Grid.origin.y;
    return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
  }
  constructor(public scale: number) {}
}

let grid1 = new Grid(1.0); // 1x scale
let grid2 = new Grid(5.0); // 5x scale
console.log(Grid.origin); // {x: 0, y: 0}

抽象类

抽象类作为其他派生类的基类使用,不能直接被实例化:

abstract class Animal {
  abstract makeSound(): void;
  move(): void {
    console.log("roaming the earth...");
  }
}

class Dog extends Animal {
  makeSound(): void {
    console.log("Woof! Woof!");
  }
}

// let animal = new Animal(); // 错误,抽象类不能被实例化
let dog = new Dog();
dog.makeSound(); // "Woof! Woof!"

函数 function

函数(Function) 是 JavaScript 应用程序的基础,TypeScript 为 JavaScript 函数添加了额外的功能。

基本语法

TypeScript 支持命名函数和匿名函数:

// 命名函数
function add(x: number, y: number): number {
  return x + y;
}

// 匿名函数
let myAdd = function (x: number, y: number): number {
  return x + y;
};

完整函数类型

函数类型包含两部分:参数类型和返回值类型:

let myAdd: (x: number, y: number) => number = function (
  x: number,
  y: number
): number {
  return x + y;
};

可选参数

在参数名后加 ? 表示该参数是可选的:

function buildName(firstName: string, lastName?: string) {
  if (lastName) return firstName + " " + lastName;
  else return firstName;
}

let result1 = buildName("Bob"); // "Bob"
let result2 = buildName("Bob", "Adams"); // "Bob Adams"

默认参数

为参数提供默认值:

function buildName(firstName: string, lastName = "Smith") {
  return firstName + " " + lastName;
}

let result1 = buildName("Bob"); // "Bob Smith"
let result2 = buildName("Bob", "Adams"); // "Bob Adams"

剩余参数

使用剩余参数表示不确定数量的参数:

function buildName(firstName: string, ...restOfName: string[]) {
  return firstName + " " + restOfName.join(" ");
}

let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
// "Joseph Samuel Lucas MacKinzie"

this 和箭头函数

使用箭头函数解决 this 问题:

let deck = {
  suits: ["hearts", "spades", "clubs", "diamonds"],
  cards: Array(52),
  createCardPicker: function () {
    return () => {
      let pickedCard = Math.floor(Math.random() * 52);
      let pickedSuit = Math.floor(pickedCard / 13);
      return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
    };
  },
};

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
console.log("card: " + pickedCard.card + " of " + pickedCard.suit);

泛型 generic

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

泛型函数

function identity<T>(arg: T): T {
  return arg;
}

let output = identity<string>("myString");
let output = identity("myString"); // 类型推断

泛型约束

interface Lengthwise {
  length: number;
}

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

loggingIdentity({ length: 10, value: 3 }); // 正确

泛型类

class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
  return x + y;
};

命名空间 namespace

命名空间(Namespace) 用于组织代码,避免命名冲突。

基本语法

namespace Validation {
  export interface StringValidator {
    isAcceptable(s: string): boolean;
  }

  const lettersRegexp = /^[A-Za-z]+$/;
  const numberRegexp = /^[0-9]+$/;

  export class LettersOnlyValidator implements StringValidator {
    isAcceptable(s: string) {
      return lettersRegexp.test(s);
    }
  }

  export class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
      return s.length === 5 && numberRegexp.test(s);
    }
  }
}

// 使用命名空间
let strings = ["Hello", "98052", "101"];
let validators: { [s: string]: Validation.StringValidator } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();

for (let s of strings) {
  for (let name in validators) {
    console.log(
      `"${s}" - ${
        validators[name].isAcceptable(s) ? "matches" : "does not match"
      } ${name}`
    );
  }
}

分离文件

命名空间可以分离到多个文件:

// Validation.ts
namespace Validation {
  export interface StringValidator {
    isAcceptable(s: string): boolean;
  }
}

// LettersOnlyValidator.ts
namespace Validation {
  const lettersRegexp = /^[A-Za-z]+$/;
  export class LettersOnlyValidator implements StringValidator {
    isAcceptable(s: string) {
      return lettersRegexp.test(s);
    }
  }
}

// Test.ts
namespace Validation {
  export class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
      return s.length === 5 && /^[0-9]+$/.test(s);
    }
  }
}

// 使用
let validator = new Validation.ZipCodeValidator();

别名

使用 import 给常用的对象起别名:

namespace Shapes {
  export namespace Polygons {
    export class Triangle {}
    export class Square {}
  }
}

import polygons = Shapes.Polygons;
let sq = new polygons.Square();