进阶

PPG007 ... 2023-5-5 About 8 min

# 进阶

# 类型别名

类型别名用于给一个类型起个新名字:

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): string {
    if (typeof n == 'string') {
        return n as Name;
    }
    return n();
}
console.log(getName('ppg007'))
console.log(getName(() => { return 'PPG007' }))
1
2
3
4
5
6
7
8
9
10
11

# 字符串字面量类型

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

type language = 'Java' | 'TypeScript';
function show(l: language): void {
    console.log(l);
}
show('Java')
1
2
3
4
5

# 元组

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

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

console.log(tom[0].toLowerCase());
console.log(tom[1].toFixed());
1
2
3
4

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

let tom: [string, number] = ['', 0];
tom.push(20);
console.log(tom);
1
2
3

# 枚举

enum WeekDay {
    Sunday,
    Monday,
    TuesDay,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
}
console.log(WeekDay["Monday"] === 1); // true
console.log(WeekDay["TuesDay"] === 2); // true
console.log(WeekDay["Saturday"] === 6); // true

console.log(WeekDay[1] === "Monday"); // true
console.log(WeekDay[2] === "TuesDay"); // true
console.log(WeekDay[6] === "Saturday"); // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

也可以给枚举项手动赋值,未手动赋值的项的递增步长仍为 1。

enum WeekDay {
    Monday = 1,
    TuesDay,
    Wednesday,
    Thursday,
    Friday,
    // 手动赋值的枚举项可以不是数字,此时需要使用类型断言来让 tsc 无视类型检查
    Saturday = <any>'6',
}
console.log(WeekDay["Monday"] === 1); // true
console.log(WeekDay["TuesDay"] === 2); // true
console.log(WeekDay["Saturday"] === 6); // false

console.log(WeekDay[1] === "Monday"); // true
console.log(WeekDay[2] === "TuesDay"); // true
console.log(WeekDay['6'] === "Saturday"); // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 常数项和计算所得项

枚举项有两种类型:常数项(constant member)和计算所得项(computed member)。

计算所得项示例:

enum Color {
    Red,
    Green,
    Blue = 'blue'.length,
}
1
2
3
4
5

Note

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

enum Color {
    Red,
    Green,
    Blue = 'blue'.length,
    Yellow,
}
1
2
3
4
5
6

当满足以下条件时,枚举成员被当作是常数:

  • 不具有初始化函数并且之前的枚举成员是常数。在这种情况下,当前枚举成员的值为上一个枚举成员的值加 1。但第一个枚举元素是个例外。如果它没有初始化方法,那么它的初始值为 0。
  • 枚举成员使用常数枚举表达式初始化。常数枚举表达式是 TypeScript 表达式的子集,它可以在编译阶段求值。当一个表达式满足下面条件之一时,它就是一个常数枚举表达式:
    • 数字字面量。
    • 引用之前定义的常数枚举成员(可以是在不同的枚举类型中定义的)如果这个成员是在同一个枚举类型中定义的,可以使用非限定名来引用。
    • 带括号的常数枚举表达式。
    • +, -, ~ 一元运算符应用于常数枚举表达式。
    • +, -, *, /, %, <<, >>, >>>, &, |, ^ 二元运算符,常数枚举表达式做为其一个操作对象。若常数枚举表达式求值后为 NaN 或 Infinity,则会在编译阶段报错。 所有其它情况的枚举成员被当作是需要计算得出的值。

# 常数枚举

常数枚举是使用 const enum 定义的枚举类型,常数枚举与普通枚举的区别是,它会在编译阶段被删除,并且不能包含计算成员。

const enum color {
    Red,
    Green,
    Blue
}
console.log(color.Blue);
1
2
3
4
5
6

编译后:

console.log(2 /* color.Blue */);
1

# 外部枚举

参考npm 包声明文件 export default

#

ES6 的类

Reference (opens new window)

ES7 中有一些关于类的提案,TypeScript 也实现了它们。

实例属性:

ES6 中实例的属性只能通过构造函数中的 this.xxx 来定义,ES7 提案中可以直接在类里面定义。

class Animal {
    name = 'Jack';

    constructor() {
        // ...
    }
}

let a = new Animal();
console.log(a.name); // Jack
1
2
3
4
5
6
7
8
9
10

静态属性:

ES7 提案中,可以使用 static 定义一个静态属性。

class Animal {
    static num = 42;

    constructor() {
        // ...
    }
}

console.log(Animal.num); // 42
1
2
3
4
5
6
7
8
9

# 修饰符

TypeScript 可以使用三种访问修饰符:

  • public:修饰的属性或方法是公有的,可以在任何地方被访问到,默认修饰符。
  • private:修饰的属性或方法是私有的,不能在声明它的类的外部访问。
  • protocted:修饰的属性或方法是受保护的,子类中也允许访问。
class Animal {
    protected name: string;
    public getName(): string {
        return this.name;
    }
    public constructor(name: string) {
        this.name = name;
    }
}

class Cat extends Animal {
    public constructor(){
        super('cat');
        console.log(this.name);
    }
}

console.log(new Animal('123'));
console.log(new Cat());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Tips

当构造函数修饰为 private 时,该类不允许被继承或者实例化。

当构造函数修饰为 protected 时,该类只允许被继承。

# 参数属性

修饰符和readonly还可以使用在构造函数参数中,等同于类中定义该属性同时给该属性赋值,使代码更简洁。

class Animal {
    public getName(): string {
        return this.name;
    }
    public constructor(private name: string) {
        this.name = name;
    }
}
console.log(new Animal('123').getName());
1
2
3
4
5
6
7
8
9

# readonly

只读属性关键字,只允许出现在属性声明或索引签名或构造函数中。

class Animal {
    readonly name: string;
    public constructor(name: string) {
        this.name = name;
    }
}

let a = new Animal('Jack');
console.log(a.name);
a.name = '123' // error
1
2
3
4
5
6
7
8
9
10

Note

如果 readonly 和其他访问修饰符同时存在的话,需要写在其后面。

class Animal {
    public constructor(public readonly name: string) {
        this.name = name;
    }
}
1
2
3
4
5

# 抽象类

抽象类不能被实例化,抽象方法必须由子类实现。

abstract class Animal {
    constructor(private readonly name: string) {
        this.name = name;
    }
    public getName(): string{
        return this.name;
    }
    public abstract say():void;
}

class Cat extends Animal {
    public say(): void {
        console.log('miao');
    }
}

let tom: Animal = new Cat('tom');
tom.say();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 类与接口

# 类实现接口

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

let a: Alarm;
a = new SecurityDoor();
a.alert();
a = new Car();
a.alert();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

实现多个接口:

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

let car = new Car();
let a: Alarm = car;
let l: Light = car;
a.alert();
l.lightOn();
l.lightOff();
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

# 接口继承接口

interface Alarm {
    alert(): void;
}

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

class Car implements Light {
    alert() {
        console.log('Car alert');
    }
    lightOn() {
        console.log('Car light on');
    }
    lightOff() {
        console.log('Car light off');
    }
}

let car = new Car();
let l: Light = car;
l.lightOn();
l.lightOff();
l.alert();
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 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 };
1
2
3
4
5
6
7
8
9
10
11
12
13
14

在声明 class Point 时,除了会创建一个名为 Point 的类,还会创建一个名为 Point 的类型,所以接口继承类就相当于接口继承接口。

Tips

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

# 泛型

简单例子:

function createArray<T>(): Array<T> {
    return [];
}
let intArray = createArray<number>();
intArray.push(1);
console.log(intArray);
1
2
3
4
5
6

# 多个类型参数

function createTuple<A, B>(a: A, b: B) :[A, B] {
    return [a, b]
}

let a = createTuple<number, boolean>(2, true);
a.pop();
console.log(a);
1
2
3
4
5
6
7

# 泛型约束

在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性,这时,我们可以对泛型进行约束,只允许这个函数传入那些包含 length 属性的变量。这就是泛型约束:

interface Alert {
    alert(): string;
}

function log<T extends Alert>(a: T): void {
    console.log(a.alert());
}

class Door implements Alert {
    public alert(): string {
        return 'Door alert'
    }
}

log<Door>(new Door());
log(new Door);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

多个类型参数之间也可以互相约束,要求 T 继承 U,这样就保证了 U 上不会出现 T 中不存在的字段:

function copyFields<T extends U, U>(target: T, source: U): T {
    for (let id in source) {
        target[id] = (<T>source)[id];
    }
    return target;
}

let x = { a: 1, b: 2, c: 3, d: 4 };
copyFields(x, { b: 10, d: 20 });
console.log(x);
1
2
3
4
5
6
7
8
9
10

# 泛型接口

import { log } from "console";

interface createArrayFunc {
    <T>(): T[]
}

let fn: createArrayFunc = function<T>(): T[]{
    return [];
}

let arr = fn<boolean>();
arr.push(false);
log(arr);
1
2
3
4
5
6
7
8
9
10
11
12
13

泛型约束也可以放到接口上:

interface CreateArr<T> {
    create(...items: Array<T>): Array<T>
    createEmpty(): Array<T>
}

class CreateNumberArr implements CreateArr<number> {
    public create(...items: number[]): number[] {
        return items;
    }
    public createEmpty(): number[] {
        return [];
    }
}

const fn = new CreateNumberArr();
console.log(fn.create(1, 2, 3));
console.log(fn.createEmpty().push(4))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 泛型类

class ResponseResult<T> {
    code: number;
    data: T;
    message: string;
}

let resp = new ResponseResult<Map<string, string>>();
resp.code = 200;
resp.message = 'OK';
let data = new Map<string, string>();
data.set('key', 'value');
resp.data = data;
1
2
3
4
5
6
7
8
9
10
11
12

# 泛型参数的默认类型

在 TypeScript 2.3 以后,我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。

class ResponseResult<T = string> {
    code: number;
    data: T;
    message: string;
}

let resp = new ResponseResult();
resp.code = 200;
resp.message = 'OK';
resp.data = '123';
console.log(JSON.stringify(resp));
1
2
3
4
5
6
7
8
9
10
11

# 声明合并

# 函数的合并

可以使用重载定义多个函数类型。

# 接口的合并

接口中的属性在合并时会简单的合并到一个接口中:

interface Alarm {
    price: number;
}
interface Alarm {
    weight: number;
}
1
2
3
4
5
6

相当于:

interface Alarm {
    price: number;
    weight: number;
}
1
2
3
4

Note

合并的属性的类型必须是唯一的。

interface Alarm {
    price: number;
}
interface Alarm {
    price: number;  // 虽然重复了,但是类型都是 `number`,所以不会报错
    weight: number;
}
1
2
3
4
5
6
7
interface Alarm {
    price: number;
}
interface Alarm {
    price: string;  // 类型不一致,会报错
    weight: number;
}
1
2
3
4
5
6
7

接口中方法的合并,与函数的合并一样:

interface Alarm {
    price: number;
    alert(s: string): string;
}
interface Alarm {
    weight: number;
    alert(s: string, n: number): string;
}
1
2
3
4
5
6
7
8

相当于:

interface Alarm {
    price: number;
    weight: number;
    alert(s: string): string;
    alert(s: string, n: number): string;
}
1
2
3
4
5
6

# 类的合并

类的合并与接口的合并规则一致。

Last update: May 5, 2023 08:35
Contributors: Koston Zhuang