# TypeScript

# 基础类型

let isDone: boolean = false;

let decLiteral: number = 6;

let name: string = "bob";

let list: number[] = [1, 2, 3];
// 数组泛型
let list: Array<number> = [1, 2, 3];

// 元组
let x: [string, number] = ['曹泽鹏', 27]

// 枚举
// 默认下标从0开始
enum Color {Red, Green, Blue}
let color:Color = Color.Green

// 手动指定下标
enum Color {Red = 1, Green=100, Blue}
let c: Color = Color.Green;

// Void void类型像是与any类型相反,它表示没有任何类型

function warnUser(): void {
    console.log("This is my warning message");
}
// Never never类型表示的是那些永不存在的值的类型

function error(message: string): never {
    throw new Error(message);
}

// 类型断言
let someValue: any = "this is a string";

let strLength: number = (<string>someValue).length;
let strLength: number = (someValue as string).length;
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
35
36
37
38

# 接口 interface

变量不可变用const ,属性不可变用readonly

interface Person {
    name: string;
    readonly sex : string; // 只读
    age?: number,  // 可选
    [propName:string]:any
}
  
let czp:Person = {
    name:'曹泽鹏',
    sex:'男',
    age: 27,
    famous:23434,
    famous1:23434,
    famouse1:23434
}


let a: number[] = [1, 2, 3, 4];
let fav: ReadonlyArray<number> = a;


// 函数类型

interface SearchFunc {
    (source: string, subString: string): boolean;
}
let searchFunc:SearchFunc = function(src: string, sub: string): boolean {
    return true
}

let searchFunc1:SearchFunc = function(src, sub): boolean {
    return true
}

// 可索引的类型
class Animal {
    name: string | undefined;
    length: number| undefined;
}
class Dog extends Animal {
    constructor() {
        super()
    }
    breed: string| undefined;
}
// 数字索引的返回值必须是字符串索引返回值类型的子类型
interface xxx {
    [x: number]: Dog; // 数字索引
    [x: string]: Animal; // 字符串索引
}

// 只读索引
interface ReadonlyStringArray {
    readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error!


// 类实现接口
interface ClockInterface {
    currentTime: Date | undefined;
    setTime(d: Date):void;
}

class Clock implements ClockInterface {
    currentTime: Date | undefined;
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, m: number) { }
}

// 接口继承接口
interface Shape {
    color: string;
}
interface PenStroke {
    penWidth: number;
}
interface Square extends Shape, PenStroke {
    sideLength: number;
}
let square = <Square>{}; // 类型断言

square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;


// 混合类型
interface Counter {
    (start: number): string;
    interval: number;
    reset(age:number): void;
}

function getCounter(): Counter{
    var counter = <Counter>function(start:number) { }
    counter.interval = 100
    counter.reset = function (params:number) {}
    return counter
}



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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106

#

class Animal {
    static own = '曹泽鹏'
    private age: number; // 私有属性不能再类的实例及子类中访问
    public name: string; // 属性默认是public
    protected sex = 'nv' // protected
    readonly fav: string[] = ['肉', '粑粑'] // 只读
    private _fullName!: string;
    protected constructor(
            age: number,
            name:string,
            sex = '男'
    ) {
        this.age = age; 
        this.name = name
        this.sex = sex
    }
    say() {
        console.log('父类say')
    }

    get fullName():string {
        return this._fullName;
    }
    set fullName(newName: string) {
        if (newName.indexOf('-') > -1) {
            this._fullName = newName;
        }
        else {
            console.log("名字必须带有 '-' ");
        }
    }
}

class Rhino extends Animal {
    // private department: string
    constructor(
        name: string,
        private department: string // 这样写就不用在上面手动声明及赋值了,把声明和赋值合并至一处。
    ) {
        super(100, name);
        this.department = department;
    }

    public getElevatorPitch() {
        // protected 声明的sex 属性 仍可以在子类访问
        // Rhino.own 静态属性也会继承 
        const src = Rhino.own + "Hello, my name is " + this.name + " and I work in " + this.department + " and i am " + this.sex;
        console.log(src);
        return src
    }
    say() {
        console.log('子类say')
        super.say()
    }
}

class Employee {
    private age: number | undefined; // 私有属性不能再类的实例里调用
    public name: string | undefined; // 属性默认是public
    protected sex = 'nv' // 用protected 定义的属性,在子类仍然可以使用
    constructor(theName: string) { this.name = theName; }
}

// 把类当做接口使用
interface EmployeeSub extends Employee {
    z: number;
}



let animal = new Animal(100, '金毛'); // Error: 因为Animal 的 constructor被声明为 protected,所以它只能被继承不能被实例化
let rhino:Rhino = new Rhino('科技', '短腿组');
let rhino1 :typeof Rhino = Rhino // typeof Rhino  取Rhino类的类型,而不是实例的类型构造函数的类型。 这个类型包含了Rhino类的所有静态成员和构造函数
rhino.own
rhino1.own
rhino.fullName = 'erewre'
rhino.getElevatorPitch()
rhino.fav = [] //Error : 只读属性不可修改
let employee = new Employee("Bob");
animal = rhino;
animal = employee; //Error: Animal 与 Employee 不兼容. 因为private age。
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

# 函数

函数类型 和 函数重载

function createZoo(): Animal[] {
    return [new Rhino(), new Elephant(), new Snake()];
}
1
2
3

# 泛型

我们把identity函数叫做泛型,因为它可以适用于多个类型。 不同于使用 any,它不会丢失信息,像第一个例子那像保持准确性,传入数值类型并返回数值类型。


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

let myIdentity: <T>(arg: T) => T = identity;

let myIdentity: <U>(arg: U) => U = identity;

let myIdentity: {<T>(arg: T): T} = identity; // 使用带有调用签名的对象字面量来定义泛型函数:

let output = identity<string>("myString"); // 手动指定类型
let output1 = identity("myString");  // ts自动推断类型
1
2
3
4
5
6
7
8
9
10
11
12
13

可以这样理解loggingIdentity的类型:泛型函数loggingIdentity,接收类型参数T和参数arg,它是个元素类型是T的数组,并返回元素类型是T的数组。 如果我们传入数字数组,将返回一个数字数组,因为此时 T的的类型为number。 这可以让我们把泛型变量T当做类型的一部分使用,而不是整个类型,增加了灵活性。

function loggingIdentity<T>(arg: T[]): T[] {
    console.log(arg.length);  // Error: T doesn't have .length
    return arg;
}

function loggingIdentity<T>(arg: Array<T>): Array<T>  {
    console.log(arg.length);  // Array has a .length, so no more error
    return arg;
}
1
2
3
4
5
6
7
8
9

自定义泛型函数

// 菜鸟版
function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: {<T>(arg: T): T} = identity;


// 初级版
interface GenericIdentityFn {
    <T>(arg: T): T;
}

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

let myIdentity: GenericIdentityFn = identity;

// 高级版

我们可能想把泛型参数当作整个接口的一个参数。 
这样我们就能清楚的知道使用的具体是哪个泛型类型(比如: GenericIdentityFn<string>而不只是GenericIdentityFn)。 
这样接口里的其它成员也能知道这个参数的类型了。

interface GenericIdentityFn<T> {
    (arg: T): T;
}

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

let myIdentityNum: GenericIdentityFn<number> = identity;
let myIdentityStr: GenericIdentityFn<string> = identity;


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
35
36
37

泛型类使用( <>)括起泛型类型

class GenericNumber<T> {
    // static own:T // 静态属性不能是泛型
    zeroValue!: T;
    name!:string;
    add!: (x: T, y: T) => T;
}

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

let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function(x, y) { return x + y; };

console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

泛型约束,我们定义一个接口来描述约束条件。 创建一个包含 .length属性的接口,使用这个接口和extends关键字来实现约束:

interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // Now we know it has a .length property, so no more error
    return arg;
}

现在这个泛型函数被定义了约束,因此它不再是适用于任意类型

loggingIdentity(3);// Error

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

在泛型约束中使用类型参数

你可以声明一个类型参数,且它被另一个类型参数所约束。 比如,现在我们想要用属性名从对象里获取这个属性。 并且我们想要确保这个属性存在于对象 obj上,因此我们需要在这两个类型之间使用约束。

function getProperty(obj: T, key: K) {
    return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };

getProperty(x, "a"); // okay
getProperty(x, "m");
1
2
3
4
5
6
7
8

# 类型兼容性

如果x要兼容y,那么y至少具有与x相同的属性


interface Named {
    name: string;
}

let x: Named;
// y's inferred type is { name: string; location: string; }
let y = { name: 'Alice', location: 'Seattle' };
x = y;

function greet(n: Named) {
    console.log('Hello, ' + n.name);
}
greet(y); // OK

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

判断两个函数是兼容的


let x = (a: number) => 0;
let y = (b: number, s: string) => 0;

y = x; // OK
x = y; // Error // 参数不兼容 


let x = () => ({name: 'Alice'});
let y = () => ({name: 'Alice', location: 'Seattle'}); // 返回值不兼容

1
2
3
4
5
6
7
8
9
10
11

# 高级类型

# 交叉类型(Intersection Types) &

如果一个值是交叉类型,我们可以访问此联合类型的所有类型。

interface Bird {
    fly():void;
    layEggs():void;
}
interface Fish {
    swim():void;
    layEggs():void;
}
function getSmallPet(): Fish & Bird {
    // ...
    return {
        fly: () => {},
        layEggs: () => {},
        swim: () => {}
    }
}

let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim();    // errors
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 联合类型(Union Types) |

如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员。

interface Bird {
    fly():void;
    layEggs():void;
}

interface Fish {
    swim():void;
    layEggs():void;
}

function getSmallPet(): Fish | Bird {
    // ...
    return {
        fly: () => {},
        layEggs: () => {},
        swim: () => {}
    }
    
}

let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim();    // errors
pet.fly();    // errors

(<Fish>pet).swim();  // 指定类型 正常工作
(<Bird>pet).fly(); // 指定类型 正常工作


function isFish(pet: Fish | Bird): pet is Fish {
    return (<Fish>pet).swim !== undefined;
}

// 用户自定义的类型保护

if (isFish(pet)) {
    pet.swim();
}
else {
    pet.fly();
}
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
35
36
37
38
39
40
41

# 可以为null的类型

name!从 name的类型里去除了 null和 undefined:


  function fixed(name: string | null): string {
    function postfix(epithet: string) {
      return name!.charAt(0) + '.  the ' + epithet; // ok
    }
    name = name || "Bob";
    return postfix("great");
  }
1
2
3
4
5
6
7
8

# 类型别名

类型别名会给一个类型起个新名字。 类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。

类型别名不能出现在声明右侧的任何地方type Yikes = Array<Yikes>; // error


type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;

function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n;
    }
    else {
        return n();
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14

类型别名也可以是泛型type Container<T> = { value: T };

我们也可以使用类型别名来在属性里引用自己:

type Tree<T> = {
    value: T;
    left: Tree<T>;
    right: Tree<T>;
}
1
2
3
4
5

与交叉类型一起使用,我们可以创建出一些十分稀奇古怪的类型。

type LinkedList<T> = T & { next: LinkedList<T> };

interface Person {
    name: string;
}

var people: LinkedList<Person>;
var s = people.name;
var s = people.next.name;
var s = people.next.next.name;
var s = people.next.next.next.name;
1
2
3
4
5
6
7
8
9
10
11

如果你无法通过接口来描述一个类型并且需要使用联合类型或元组类型,这时通常会使用类型别名。

# 字符串字面量类型

type Easing = "ease-in" | "ease-out" | "ease-in-out";
class UIElement {
    animate(dx: number, dy: number, easing: Easing) {
    }
}

let button = new UIElement();
button.animate(0, 0, "ease-in");
button.animate(0, 0, "uneasy"); // error: "uneasy" is not allowed here
1
2
3
4
5
6
7
8
9

# 索引类型(Index types)

function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] {
    return names.map(n => o[n]);
}

interface Person {
    name: string;
    age: number;
}
let person: Person = {
    name: 'Jarid',
    age: 35
};
let str: string[] = pluck(person, ['name']); // ok, string[]
1
2
3
4
5
6
7
8
9
10
11
12
13

# 三斜线指令

/// <reference path="..." /> 指令是三斜线指令中最常见的一种。 它用于声明文件间的 依赖。

三斜线引用告诉编译器在编译过程中要引入的额外的文件。

/// <reference types="..." />/// <reference path="..." />指令相似,

这个指令是用来声明 依赖的; 一个 /// <reference types="..." />指令则声明了对某个包的依赖。

例如,把 /// <reference types="node" />引入到声明文件,表明这个文件使用了 @types/node/index.d.ts里面声明的名字;

并且,这个包需要在编译阶段与声明文件一起被包含进来。

# 文档

  1. https://www.typescriptlang.org/
  2. https://www.tslang.cn/docs/home.html