Typescript 语言简介, 以学习 React 开发.

有如下两种方式可以执行 ts 文件:

  • 用 ts-node 来执行相关的文件.
  • 用 npm 初始化, 然后 npm start 的方式.

基本数据类型

  • boolean
  • number
  • bigint
  • array
  • tuple
  • enum
  • any
  • void
  • undefined
  • null
  • never

特殊类型: object, 推测在 ts 中表示可选类型是这样的: (可空的类型 | null)

类型转换

使用 as 进行数据类型转换.

接口

ts
interface LabeledObject {
    label: string
}

function printLabelWith(obj: LabeledObject) {
    console.log(obj.label)
}

接口可以定义可选的属性.

类的简单定义如下所示:

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

let greeter = new Greeter("world");

其中构造方法使用 constructor, 另外方法不需要添加 function 来修饰. 在 ts 中新建对象仍然使用 new 关键字.

继承的写法如下:

ts
class Animal {
    move(distanceInMeters: number = 0) {
        console.log(`Animal moved ${distanceInMeters}m.`);
    }
}

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

另外方法的覆盖直接写同名方法即可, 不需要添加 override, 这个和 oc 的语法类似:

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

class Snake extends Animal {
    constructor(name: string) { super(name); }
    move(distanceInMeters = 5) {
        console.log("Slithering...");
        super.move(distanceInMeters);
    }
}

class Horse extends Animal {
    constructor(name: string) { super(name); }
    move(distanceInMeters = 45) {
        console.log("Galloping...");
        super.move(distanceInMeters);
    }
}

属性或方法如果没有添加访问修饰, 则默认是 public 的, 在 ts 中有 public/private/protected 三种访问修饰.

在类中仍然可以拥有 static 属性.

在 ts 中可以声明抽象类, 使用 abstract 语法.

在 ts 中还可以使用 class 作为接口的父接口使用, 这个类似 dart 中使用 class 作为接口的用法:

ts
class Point {
    x: number;
    y: number;
}

interface Point3d extends Point {
    z: number;
}

let point3d: Point3d = {x: 1, y: 2, z: 3};

泛型

泛型工厂方法的实现: 居然可以这样来避免外部直接使用 new, 而且是泛型的

ts
function create<T>(c: {new(): T; }): T {
    return new c();
}

更复杂的例子可以是这样的:

ts
class BeeKeeper {
    hasMask: boolean;
}

class ZooKeeper {
    nametag: string;
}

class Animal {
    numLegs: number;
}

class Bee extends Animal {
    keeper: BeeKeeper;
}

class Lion extends Animal {
    keeper: ZooKeeper;
}

function createInstance<A extends Animal>(c: new () => A): A {
    return new c();
}

createInstance(Lion).keeper.nametag;  // typechecks!
createInstance(Bee).keeper.hasMask;   // typechecks!

模块(Module)

模块是独立的程序单元, 在其中的类, 对象, 变量不会被暴露在全局范围, 除非使用 export 显式暴露. 使用者通过 import 语法来使用被 export 的内容.

在 TS/JS 中, 如果顶层包含 import/export 的文件, 就认为是一个 module.

不包含 import/export 的就认为是脚本, 它里面的内容是全局的.

export 的语法:

ts
export const numberRegexp = /^[0-9]+$/;

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

// 如果要重命名, 可以使用下面的语法:

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

// 通过某个模块暴露其它模块的内容(类似一个汇总的 header), 可以像下面这样:

export class ParseIntBasedZipCodeValidator {
    isAcceptable(s: string) {
        return s.length === 5 && parseInt(s).toString() === s;
    }
}

// Export original validator but rename it
export {ZipCodeValidator as RegExpBasedZipCodeValidator} from "./ZipCodeValidator";

// 或者是直接 export 另外一个模块的全部内容, 也是相当于汇总的:

export * from "./StringValidator"; // exports 'StringValidator' interface
export * from "./ZipCodeValidator";  // exports 'ZipCodeValidator' and const 'numberRegexp' class
export * from "./ParseIntBasedZipCodeValidator";

import 的语法:

ts
// 引入单一实体:

import { ZipCodeValidator } from "./ZipCodeValidator";

let myValidator = new ZipCodeValidator();

// 在引入后重命名:

import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";
let myValidator = new ZCV();

// 从一个模块中引入所有内容, 作为一个变量使用:

import * as validator from "./ZipCodeValidator";
let myValidator = new validator.ZipCodeValidator();

// 比如 React 中引入某个 css 文件, 以便实现一些副作用, 可以这样:

import "./my.css";

另外每个模块都可以包含唯一的 export default:

ts
export default class ZipCodeValidator {
    static numberRegexp = /^[0-9]+$/;
    isAcceptable(s: string) {
        return s.length === 5 && ZipCodeValidator.numberRegexp.test(s);
    }
}

import validator from "./ZipCodeValidator"; // 默认引入的就是 default 的那个.

let myValidator = new validator();

名字空间

在 ts 中可以使用和 cs 类似的名字空间的概念. 通过名字空间来组织代码, 另外还可以将一个名字空间分布到多个文件中, 只需要添加 reference tag:

Validation.ts:

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

LettersOnlyValidator.ts:

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

ZipCodeValidator.ts:

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

需要认识到名字空间和 module 是两种不同的模块组织形式.