Typescript 基础

本文最后更新于 2021年4月4日 晚上

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

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

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

基本数据类型

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

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

类型转换

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

接口

1
2
3
4
5
6
7
interface LabeledObject {
label: string
}

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

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

类的简单定义如下所示:

1
2
3
4
5
6
7
8
9
10
11
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}

let greeter = new Greeter("world");

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

继承的写法如下:

1
2
3
4
5
6
7
8
9
10
11
class Animal {
move(distanceInMeters: number = 0) {
console.log(`Animal moved ${distanceInMeters}m.`);
}
}

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 作为接口的用法:

1
2
3
4
5
6
7
8
9
10
class Point {
x: number;
y: number;
}

interface Point3d extends Point {
z: number;
}

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

泛型

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

1
2
3
function create<T>(c: {new(): T; }): T {
return new c();
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 的语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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 的语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 引入单一实体:

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:

1
2
3
4
5
6
7
8
9
10
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:

1
2
3
4
5
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
}

LettersOnlyValidator.ts:

1
2
3
4
5
6
7
8
9
/// <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:

1
2
3
4
5
6
7
8
9
/// <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 是两种不同的模块组织形式.


Typescript 基础
https://blog.rayy.top/2020/11/15/2020-11-15-ts-basics/
作者
貘鸣
发布于
2020年11月15日
许可协议