# TypeScript基础

  1. ts官方文档 (opens new window)

# 1、简介

javaScript是一门动态的弱类型语言,对变量的类型非常宽容,而且不会在这些变量和它们的调用者之间建立结构化的契约。在ES标准推出静态类型检查之前,ts是当下解决此问题的最佳方案。

相关库:flow(faceBook,相关产品yarn,jest)、TypeScript(微软产品,发展较好)

TypeScript是拥有类型系统的javascript的超集,可编译为纯javascript,相关特点:

  • 类型检查
  • 语言扩展:装饰器、接口等
  • 工具属性:可编译为标准js

使用TypeScript的其他好处:

  • 接口定义代替文档
  • IDE能够提高开发效率,降低维护成本:vscode有相关补全插件
  • 重塑“类型思维”

# 2、类型基础

# 1、强类型和弱类型

  • 强类型语言不允许改变变量的数据类型,除非进行强制类型转换
  • 弱类型语言中,变量可以被赋予不同的数据类型

# 2、静态类型和动态类型

  • 静态类型语言:在编译阶段确定所有变量的类型
  • 动态类型语言:在执行阶段确定所有变量的类型

TIP

javascript和c++的执行比较

  1. javascript
    1. 在程序运行时,动态计算属性偏移量
    2. 需要额外的空间存储属性名
    3. 所有对象的偏移量信息各寸一份
  2. c++
    1. 编译阶段确定属性偏移量
    2. 用偏移量访问代替属性名访问
    3. 偏移量信息共享

# 3、基本类型

ES6的数据类型

  • 6种简单类型:Boolean、Number、String、undefined、null、Symbol
  • 引用数据类型:Object(Array、Function)

TypeScript的数据类型:

  • 6种简单类型:Boolean、Number、String、undefined、null、Symbol
  • 引用数据类型:Object(Array、Function)
  • 新增:void、any、never、元组、枚举、高级类型

类型注解:相当于类型声明,语法:(变量/函数) : type

//原始类型
let bool: boolean = true
let num: number = 123
let str: string = 'abc'

//数组类型:两种方式type[]\Array<type>
//Array为ts预定的泛型接口
//联合类型:|
let arr1: number[] = [1, 2, 3]
let arr2: Array<number> = [1, 2, 3]
let arr3: Array<number | string> = [1, 2, 3, '4']

//元组:限定元素个数与类型的数组
//修改元素类型,以及超过数量都报错
//越界问题:可通过push添加元素,但无法访问
//eg:tuple.push(2) 不报错 tuple[2]报错
let tuple: [number, string] = [0, '1']

//函数
let add = (x:number, y:number) => x + y
//函数类型,无实现
let compute: (x:number, y:number) => number
compute = (a, b) => a + b

//对象
/* 
let obj: object = {x: 1, y: 2}
obj.x = 3 报错
*/
let obj: {x: number, y: number} = {x: 1, y: 2}
obj.x = 3

//symbol:独一无二的值
let s1: symbol = Symbol()
let s2 = Symbol()
console.log(s1 === s2) //false

//undefined未定义, null没有值
//un和nu只能赋值为自己,不能赋值为其他类型
//官方文档,undefined和null、never为任何类型的子类型
//其他类型可以赋值为null或undefined,需开启ts配置:"strictNullChecks": false
//num = null 或 num = undefined
//或使用联合类型:let num: number | undefined | null = 3
let un: undefined = undefined
let nu: null = null

//js中void为操作符,让任何变量返回undefined
//eg:获取安全的undefined,void 0
//ts中void为没有任何返回值的类型
let noReturn = () => {}

//any: 任意类型
let x

//never:永远没有返回值的类型,报错或者死循环
let error = () => {
    throw new Error('error')
}
let endless = () => {
    while(true) {}
}
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

# 4、枚举类型

枚举:一组有名字的常量集合

使用场景及思维模式:将程序不容易记忆的硬编码或未来可变的常量提取出来,定义为枚举类型,提高程序可读性和可维护性

# 1、枚举类型

  • 数字枚举:枚举成员默认从0递增,实现为反向映射
  • 字符串枚举:不支持反向映射
  • 异构枚举:数字和字符串混合,不推荐使用
//数字枚举:枚举成员默认从0递增
enum Role {
    Reporter,
    Developer,
    Maintainer,
    Owner,
    Guest
}
console.log(Role.Reporter) //0

//可定义初始值
enum Role1 {
    Reporter,
    Developer=2,
    Maintainer,
    Owner,
    Guest
}

console.log(Role1.Reporter)   //0
console.log(Role1.Developer)  //2
console.log(Role1.Maintainer) //3

console.log(Role) //对象
/* 
{
    0: "Reporter"
    1: "Developer"
    2: "Maintainer"
    3: "Owner"
    4: "Guest"
    Developer: 1
    Guest: 4
    Maintainer: 2
    Owner: 3
    Reporter: 0
}
*/

//实现原理:反向映射
/* 
var Role;
(function (Role) {
    Role[Role["Reporter"] = 0] = "Reporter";
    Role[Role["Developer"] = 1] = "Developer";
    Role[Role["Maintainer"] = 2] = "Maintainer";
    Role[Role["Owner"] = 3] = "Owner";
    Role[Role["Guest"] = 4] = "Guest";
})(Role || (Role = {}));
*/

//字符串枚举,不支持反向映射
enum Message{
    Success = 'succeed',
    Fail = 'fail'
}
/*
var Message;
(function (Message) {
    Message["Success"] = "succeed";
    Message["Fail"] = "fail";
})(Message || (Message = {}));
*/

//异构枚举:数字和字符串混合,不推荐使用
enum Answer {
    N,
    Y = 'Yes'
}
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

# 2、枚举成员

  • 只读属性,不可修改
  • 成员类型
    • const member:在编译阶段被计算出结果,分为3种
      • 无初始值
      • 对常量成员的引用
      • 常量表达式
    • computed member:表达式保留到程序的执行阶段
      • 非常量表达式
      • 注意:在computed成员后定义成员需赋值,否则报错
//枚举成员
//成员不可变:Char.a = 4报错
enum Char {
    //const member:在编译阶段被计算出结果
    a,          //无初始值
    b = Char.a, //对常量成员的引用
    c = 1 + 3,  //常量表达式
    //computed member:表达式保留到程序的执行阶段
    d = Math.random(), //非常量表达式
    e = '123'.length,
    //在computed成员后定义成员需赋值,否则报错
    f = 2
}

/* 
var Char;
(function (Char) {
    //常量已计算
    Char[Char["a"] = 0] = "a";
    Char[Char["b"] = 0] = "b";
    Char[Char["c"] = 4] = "c";
    //computed保留到执行
    Char[Char["d"] = Math.random()] = "d";
    Char[Char["e"] = '123'.length] = "e";
})(Char || (Char = {}));
*/
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

# 3、常量枚举

  • 用const申明,成员只能为const member

  • 在编译后会被移除

  • 使用场景:不需要使用对象,需要使用对象的值

const enum Month {
    Jan,
    Feb,
    Mar
}
let month = [Month.Jan, Month.Feb, Month.Mar]
//var month = [0 /* Jan */, 1 /* Feb */, 2 /* Mar */];
1
2
3
4
5
6
7

# 4、枚举/枚举成员作为类型

  • 无初始值
  • 初始值均为数字
  • 初始值均为字符
//枚举类型和枚举成员类型:无初始值、枚举成员均为数字、枚举成员均为字符串
//不同枚举类型不可以比较,相同类似枚举可比较 e === f
//数字枚举类型可赋值为任意数字
//字符串枚举类型只能赋值为枚举值
enum E { a, b }
enum F { a = 0, b = 1}
enum G { a = 'apple', b = 'banana'}

let e: E = 3
let f: F = 3

let e1: E.a = 1
let e2: E.b
let e3: E.a = 1
e1 === e3

let g1: G = G.b
let g2: G.a = G.a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 5、实战

/*
改造前问题:
1. 可读性差:很难记住数字的含义
2. 可维护性差:硬编码,牵一发动全身
*/
function initByRole (role: number) {
    if (role === 1 || role === 2){
        //do sth
    } else if (role === 3 || role === 4) {
        //do sth
    } else if (role === 5) {
        //do sth
    } else {
        //do sth
    }
}

//改造后,使用枚举常量
const enum Roler {
    Reporter = 1,
    Developer,
    Maintainer,
    Owner,
    Guest
}

function initByRole_byEnum (role: number) {
    if (role === Roler.Reporter || role === Roler.Developer){
        //do sth
    } else if (role === Roler.Maintainer || role === Roler.Owner) {
        //do sth
    } else if (role === Roler.Guest) {
        //do sth
    } else {
        //do sth
    }
}
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

# 5、接口

在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。

用途及思维:用接口定义api输入和输出

# 1、对象类型接口

  • 通过interface关键字定义接口,检查原则为鸭式辨型法

  • 对象字面量会进行检查,绕过方法有3种

    • 将对象字面量赋值给变量
    • 使用类型断言
    • 使用字符串索引签名
  • 对象属性

    • 只读属性:不可修改
    • 可选属性
interface List {
    readonly id: number; //只读属性
    name: string;
    age?: number; //可选属性
}
interface Result {
    data: List[]
}
function render(result: Result){
    result.data.forEach((value) => {
        console.log(value.id, value.name)
    })
}

/* 对象字面量会进行类型检查
报错,sex处标红 
render({
    data: [
        { id: 1, name: 'A', sex: 'male' },
        { id: 2, name: 'B' },
    ]
})*/

//绕过对象字面量检查的方法有三种:
//检查原则为鸭式辨型法:包含id和name即可,多余不检查
//1. 将对象字面量赋值给变量
let result = {
    data: [
        { id: 1, name: 'A', sex: 'male' },
        { id: 2, name: 'B' },
    ]
}
render(result)

//2. 使用类型断言
//告知编译器已知类型可跳过类型检查
render({
    data: [
        { id: 1, name: 'A', sex: 'male' },
        { id: 2, name: 'B' },
    ]
} as Result)
//不推荐类型断言写法,react中会产生歧义
/* render(<Result>{
    data: [
        { id: 1, name: 'A', sex: 'male' },
        { id: 2, name: 'B' },
    ]
})*/

//3. 使用字符串索引签名
/* 
interface List {
    id: number;
    name: string;
    [x: string]: any;
}
render({
    data: [
        { id: 1, name: 'A', sex: 'male' },
        { id: 2, name: 'B' },
    ]
})
*/
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

# 2、可索引类型接口

当不确定接口中属性个数时,可使用可索引类型接口

  • 数字索引:想当于数组[index: number]
  • 字符串索引:[x: string]
  • 混用时,数字索引签名的返回值必须是字符串索引签名返回值的子类型(由于js会进行类型转换,将number转换为string,从而保持类型兼容性)
//数字索引:想当于数组[index: number]
interface StringArray {
    [index: number]: string
}
let chars: StringArray = ['A', 'B']

//字符串索引
interface Names {
    [x: string]: string;
}
let names: Names = {
    name: 'Jian',
    sex: 'male'
}

//混用
interface Api {
    [index: number]: any;
    [x: string]: string;
}
let api: Api = {
    1: 'jing',
    name: 'Jian',
    sex: 'male'
}
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

# 3、函数类与混合类型接口

/*
1. 函数类型接口:
interface F { 
	(arg: type): type 
}
2. 混合类型接口
interface H { 
	(arg: type): type;
	prop: type;
	method(arg: type): type;
}
*/

//变量定义函数类型
let Add: (x: number, y: number) => number

//用接口定义函数类型
interface Add {
    (x: number, y: number): number
}

//类型别名:为函数起名字
//type Add = (x: number, y: number) => number

let add2: Add = (a, b) => a + b

//混合类型接口
interface Lib {
    (): void;
    version: string;
    doSomething(): void;
}

function getLib(){
    let lib: Lib = (() => {console.log('lib')}) as Lib;
    lib.version = '1.0'
    lib.doSomething = () => {console.log('Jian')}
    return lib;
}
//创建实例
let lib1 = getLib();
lib1()  //lib
lib1.doSomething() //Jian
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

# 4、接口和类的关系

  • 类类型接口
    • 类必须实现接口中的所有属性
    • 接口只能约束类的公有成员,不能约束私有成员、受保护成员、静态成员和构造函数
  • 接口继承接口
    • 抽离可重用的接口
    • 将多个接口整合成一个接口
  • 接口继承类
    • 抽离出类的共有成员、私有成员和受保护成员
//1、类类型接口
//接口只能约束类的公有成员,不能约束私有成员、受保护成员、静态成员和构造函数
interface Human {
   name: string;
   eat(): void;
}
//类必须实现接口中的所有属性
class Asian implements Human {
   constructor(name: string){
       this.name = name
   }
   name: string
   eat(){}
   sleep(){}
}

//2、接口继承接口
//可抽离可重用的接口
//将多个接口整合成一个接口
interface Man extends Human {
   run(): void
}
interface Child {
   cry(): void
}
interface Boy extends Man, Child {}
//需实现来自Man, Child, Human三个接口属性
let boy: Boy = {
   name: '',
   run(){},
   eat(){},
   cry(){}
}

//3、接口继承类:抽象类,只有类的成员结构没具体实现
//抽离出类的共有成员、私有成员和受保护成员
class Auto {
   state = 1
}
//接口中隐藏state属性
interface AutoInterface extends Auto{}
class C implements AutoInterface{
   state = 1
}
class Bus extends Auto implements AutoInterface{
}
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
  • 类与接口的关系
    • 接口之间可以相互继承
    • 类之间可以相互继承
    • 接口可以通过类来实现,但只能约束类的公有成员
    • 接口可以抽离出类的成员,包括public、private、protected

# 6、函数

# 1、定义

  • 定义方式
    • function
    • 变量定义
    • 类型别名
    • 接口定义
  • 类型要求
    • 参数类型必须申明
    • 返回值类型一般无需申明
// 一、函数定义方式
// 1、function
function add1(x: number, y: number) {
    return x + y
}

// 2、函数类型定义:变量定义、类型别名、接口定义
// 定义函数类型,无函数体
// 类型要求:参数类型必须声明、返回值类型一般无需申明
let add2: (x: number, y: number) => number
type add3 = (x: number, y: number) => number
interface add4 {
    (x: number, y: number): number
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 2、参数

  • 参数个数:实参和形参必须一一对应
  • 可选参数:使用?,必须参数不能位于可选参数后面
  • 默认参数:=,调用时:
    • 在必选参数前,默认参数不可省略
    • 在必选参数后,默认参数可以省略
  • 剩余参数: 以数组形式存在
//二、函数参数
//1、参数个数:实参和形参必须一一对应
//add1(1) 提示报错

//2、可选参数:使用?,必须参数不能位于可选参数后面
function add5(x: number, y?: number){
    return y ? x + y : x;
}
add5(1)

//3、默认参数:=,调用时: 
//在必选参数前,默认参数不可省略
//在必选参数后,默认参数可以省略
function add6(x: number, y=0, z: number, q = 1){
    return x + y + z + q;
}
//不传undefined, 3会赋值给y
add6(1, undefined, 3)

//4、剩余参数: 以数组形式存在
function add7(x: number, ...rest: number[]){
    return x + rest.reduce((pre, cur) => pre + cur)
}
add7(1, 2, 3, 4, 5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 3、重载

  • 静态类型语言:两个函数名称相同,参数个数或类型不同
  • ts:预先定义一组名称相同,类型不同的函数申明,并在一个类型最宽泛的版本中实现
//三、函数重载
//静态类型语言:两个函数名称相同,参数个数或类型不同
//ts:预先定义一组名称相同,类型不同的函数申明,并在一个类型最宽泛的版本中实现
function add8(...rest: number[]): number;
function add8(...rest: string[]): string;
function add8(...rest: any[]): any {
    let first = rest[0]
    if (typeof first === 'string'){
        return rest.join('')
    }
    if (typeof first === 'number'){
        return rest.reduce((pre, cur) => pre + cur)
    }
}
add8(1, 2, 3)
add8('a', 'b', 'c')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 7、类

# 1、基本实现

  • 类中定义的属性都是实例属性,类中定义的属性都是实例属性
  • 实例属性必须有初始值,或在构造函数中被赋值或为可选成员
class Dog {
    //实例属性必须有初始值,或在构造函数中被赋值或为可选成员
    constructor(name: string){
        this.name = name
    }
    name: string
    //constructor(){}
    //name?: string
    //constructor(){}
    //name: string = 'dog'
    run(){}
}
//类中定义的属性都是实例属性
//{run: f, constructor: f}
console.log(Dog.prototype)
//类中定义的属性都是实例属性
//{name: "wangwang"}
let dog = new Dog('wangwang')
console.log(dog)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 2、继承

子类的构造函数中必须包含super调用,入参包括父类参数,使用关键字extends

class Husky extends Dog {
    constructor (name: string, color: string){
        super(name)
        this.color = color;
    }
    color: string
}
1
2
3
4
5
6
7

# 3、修饰符

# 1、类成员修饰符

  • public:对所有人可见,所有成员默认为public

  • private

    • 只能在被定义类中访问,不能通过实例或子类访问
    • private constructor:不能被实例化,不能被继承,常用在单例模式
  • protected

    • 只能在被定义的类和子类中访问,不能通过实例访问
    • protected constructor:只能被实例化,不能被继承
  • readonly: 属性不可修改,必须有初始值,或在构造函数中被赋值

  • static:只能由类名调用,不能通过实例访问,可继承

class Dog {
    constructor(name: string){
        this.name = name
    }
    readonly name: string
    run(){}
  	static food: string = 'bread'
}

class Husky extends Dog {
    constructor (name: string, color: string){
        super(name)
        this.color = color;
    }
    color: string
}

console.log(Dog.food)
console.log(Husky.food)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 2、构造函数修饰符

将参数变为实例属性,更简捷

class Husky1 extends Dog {
    constructor (name: string, public color: string){
        super(name)
        this.color = color
    }
  	//成员定义可省略
    //color: string
}
1
2
3
4
5
6
7
8

# 4、抽象类

用途和思维:用于抽象出实例的共性,从而有利于代码复用和扩展,也可以用于实现多态

  • 不能被实例化,只能被继承
    • 抽象方法包含具体实现:子类直接复用
    • 抽象方法不包含具体实现:子类必须实现
  • 多态:多个子类对父抽象类的方法有不同实现,实现运行时绑定
abstract class Animal {
    //抽象方法包含具体实现:子类直接复用
    eat(){
        console.log('eat')
    }
    //抽象方法不包含具体实现:子类必须实现
    abstract sleep(): void
}
//实例化报错:抽象方法只能继承不能实例化
//let animal = new Animal()
class Cat extends Animal{
    constructor(public name: string){
        super()
        this.name = name
    }
    sleep(){
        console.log('cat sleep')
    }
}
let cat = new Cat('Jian')
cat.eat()   //eat
cat.sleep() //cat sleep

//多态:父类中定义抽象方法,在多个子类中对方法进行不同的实现
//在程序运行时,根据不同的对象实现不同的操作,从而实现运行时绑定
class Rabbit extends Animal{
    sleep(){
        console.log('rebbit sleep')
    }
}

let rabbit = new Rabbit()
let animals: Animal[] = [cat, rabbit]
//cat sleep 
//rabbit sleep
animals.forEach(i => {
    i.sleep()
})
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

# 5、this类型

类的成员方法可以返回this

  • 实现实例方法的链式调用
  • 在继承时,具有多态性,保持父子类之间接口调用的连贯性
class WorkFlow {
    step1(){
        return this
    }
    step2(){
        return this
    }
}
//链式调用
new WorkFlow().step1().step2()

//在继承时,具有多态性,保持父子类之间接口调用的连贯性
//this既可以为父类,又可以为子类
class Myflow extends WorkFlow {
    next(){
        return this;
    }
}
new Myflow().next().step1().next().step2()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 8、泛型

泛型:不预先确定的数据类型,具体的类型在使用的时候才确定

泛型的好处:

  • 增强程序的可扩展性:函数或类可以很轻松地支持多种类型
  • 增强代码的可读性:不必写多条函数重载,或者冗长的联合类型申明
  • 灵活地控制类型之间的约束

思维模式:将泛型当做函数参数对待,代表类型不代表值

# 1、支持多种类型的方法

  • 函数重载
  • 联合类型
  • any类型:丢失类型约束
  • 泛型:不预先确定的类型,使用时才确定
//1. 函数重载
function log(value: string): string;
function log(value: string[]): string[];
function log(value: any) {
    console.log(value)
    return value;
}
//2. 联合类型
function log1(value: string | string[]): string | string[] {
    console.log(value);
    return value;
}

//3. any类型:丢失类型约束
function log2(value: any){
    console.log(value);
    return value;
}

//4. 泛型
function log3<T>(value: T): T {
    console.log(value)
    return value
}
//调用:赋值T或者使用类型推断
log3<string[]>(['a', 'b'])
log3(['a', 'b'])
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

# 2、泛型函数

  • 定义:function generic<T>(arg: T):t
  • 调用
    • generic<type>(arg)
    • generic(arg):使用类型推断
  • 泛型函数类型:type Generic = <T>(arg:T)=> T
function log3<T>(value: T): T {
    console.log(value)
    return value
}
//泛型函数类型
type Log = <T>(value: T) => T
let myLog: Log = log3
1
2
3
4
5
6
7

# 3、泛型接口

  • 定义:interface Generic<T>{ (arg: T): T}
  • 实现:let generic: Generic<type>(必须指定类型)
//泛型接口:约束成员
// interface Log {
//     <T>(value: T): T
// }
//泛型接口:约束整个接口,实现时必须指定类型或设置默认类型
interface Log<T = string> {
   (value: T): T
}
let myLog: Log<number> = log3
myLog(1)
1
2
3
4
5
6
7
8
9
10

# 4、泛型类

  • 定义
    • class Generic<T>{method(value: T){}}
    • 泛型不能应用于类的静态成员
  • 实例化
    • let generic = new Generic<type>()
    • let generic = new Generic(), T可为任意类型
//泛型类
//定义:class Generic<T>{ (arg: T): T}
//注意:泛型不能应用与类的静态成员
class Log<T> {
    run(value: T){
        console.log(value)
        return value
    }
}
//实现
let loga = new Log<number>()
loga.run(1)
//未传,则T可能未任意类型
let logb = new Log()
logb.run('1')
//使用后,类型确认,只能传入string类型
//报错:logb.run(1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 5、泛型约束

T extends U:T必须具有U的属性

//泛型约束:T必须具有U的属性
interface Length{
    length: number
}
function logc<T extends Length>(value: T): T{
    console.log(value, value.length)
    return value
}
//传入的值必须有length属性
logc([1])
logc('123')
logc({length: 1})
1
2
3
4
5
6
7
8
9
10
11
12

# 9、类型检查机制

类型检查机制:Typescript编译器在做类型检查时,所秉承的一些原则,以及表现出的一些行为

作用:辅助开发,提高开发效率

内部:类型推断、类型兼容性、类型保护

# 1、类型推断

不需要指定变量的类型(或函数的返回值类型),TypeScript可以根据某些规则自动的为其推断出一个类型

  • 基础类型推断
    • 初始化变量
    • 设置函数默认参数
    • 确定函数返回值
  • 最佳通用类型推断:推断出一个可以兼容当前所有类型的通用类型
  • 上下文推断:根据事件绑定推断出事件类型

# 2、类型断言

  • 含义:用自己申明的类型覆盖类型推断
  • 方式:(推荐) 表达式 as type,不推荐<type> 表达式
  • 弊端:没有按照接口的约定赋值,不会报错
//一、基础类型推断
//1. 初始化变量
let a = 1
let b = [1]

//2. 设置函数默认参数
//3. 确定函数返回值
//x参数进行了推断
//c返回值进行了推断
let c = (x = 1) => x + 1

//二、最佳通用类型推断
//推断出一个可以兼容当前所有类型的通用类型
//d被推断出联合类型:(number | null) []
let d = [1, null]

//三、上下文推断
//根据事件绑定推断出事件类型,event的属性可进行推断
window.onkeydown = (event) => {
    console.log(event)
}

//四、类型断言
interface Foo{
    bar: number
}
let foo = {} as Foo
foo.bar = 1
//建议写法
let foo1: Foo = {
    bar: 1
}
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

# 3、类型兼容性

当一个类型Y可以被赋值给另一个类型X时,我们就可以说类型X兼容类型Y

x兼容Y:x(目标类型)= Y(源类型)

兼容原则:

  • 结构之间兼容:成员少的兼容成员多的
  • 函数之间兼容:参数多的兼容参数少的

# 1、接口兼容性

鸭式辩型法:成员少的兼容成员多的

interface X {
    a: any;
    b: any;
}
interface Y {
    a: any;
    b: any;
    c: any;
}
let g: X = {a: 1, b: 2}
let h: Y = {a: 1, b: 2, c: 3}
//g兼容h:g = h,报错:h = g
1
2
3
4
5
6
7
8
9
10
11
12

# 2、函数兼容性

需满足三个条件才可兼容:参数个数、参数类型、返回值类型

  • 参数个数:目标函数多于源函数
  • 参数类型
    • 参数为基本类型:必须匹配
    • 参数为对象
      • 严格模式:成员多的兼容成员少的
      • 非严格模式:相互兼容(函数参数双向协变)
  • 返回值类型:目标函数必须与源函数相同,或为其子类型

特殊兼容:

  1. 可选参数和剩余参数兼容,遵循原则

    1. 固定参数兼容可选参数和剩余参数
    2. 可选参数不兼容固定参数和剩余参数(严格模式)
    3. 剩余参数兼容可选参数和固定参数
  2. 函数重载:申明为目标函数,实现为源函数,目标函数需兼容实现函数

type Handler = (a: number, b: number) => void
function hof(handler: Handler){
    return handler
}

//a. 参数个数:目标函数多余源函数
let handler1 = (a: number) => {}
hof(handler1)
//let handler2 = (a: number, b: number, c: number) => {}
//报错不兼容:hof(handler2)

//可选参数和剩余参数
let a = (p1: number, p2: number) => {}
let b = (p1?: number, p2?: number) => {}
let c = (...args: number[]) => {}
//固定参数兼容可选参数和剩余参数
a = b
a = c
//可选参数不兼容固定参数和剩余参数(严格模式)
//tsconfig.json中:strictFunctionTypes: false,可兼容
b = a
b = c
//剩余参数兼容可选参数和固定参数
c = a
c = b


//b. 参数类型
//简单类型必须匹配
let handler3 = (a: string) => {}
//报错:hof(handler3)

//对象类型:
//严格模式成员多的兼容成员少的
//非严格模式:相互兼容(函数参数双向协变),精确类型赋值给不精确类型
interface Point3D{
    x: number;
    y: number;
    z: number;
}
interface Point2D{
    x: number;
    y: number;
}
let p3d = (point: Point3D) => {}
let p2d = (point: Point2D) => {}
p3d = p2d
//严格模式报错:p2d = p3d


//c. 返回值类型:目标函数必须与源函数相同,或为其子类型
let d = () => ({name: 'Alice'});
let g = () => ({name: 'Alice', location: 'Beijing'});
d = g
//不兼容:g = d

//函数重载: 申明为目标函数,实现为源函数,需兼容
function overload(a: number, b: number): number;
function overload(a: string, b: string): string;
function overload(a: any, b: any): any {}
//报错:function overload(a: any, b: any, c: any): any {}
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

# 3、枚举兼容性

  • 枚举类型和数字类型相互兼容
  • 枚举类型之间不兼容
//枚举兼容性
enum Fruit { Apple, Banana }
enum Color { Red, Yellow }
let fruit: Fruit.Apple = 3
let no: number = Fruit.Apple
//不兼容:let color: Color.Red = Fruit.Apple
1
2
3
4
5
6

# 4、类兼容性

  • 静态成员和构造函数不在比较范围
  • 两个类具有相同的实例成员,它们的实例相互兼容
  • 类中包含私有成员或受保护成员,只有父类和子类的实例相互兼容
//类兼容性
//构造函数和静态成员不做比较
class A {
    constructor(p: number, q: number){}
    id: number = 1
}
class B {
    static s = 1
    constructor(p: number){}
    id: number = 2
}
let aa = new A(1, 2)
let bb = new B(1)
aa = bb
bb = aa

//类中包含私有成员或受保护成员,只有父类和子类的实例相互兼容
class D {
    constructor(p: number, q: number){}
    id: number = 1
    private name: string = ''
}
class H extends D {}
let hh = new H(1, 2)
let dd = new D(1, 2)
dd = hh
hh = dd
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

# 5、泛型兼容性

  • 泛型接口:只有类型参数T被接口成员使用时,才会影响兼容性
  • 泛型函数:定义相同,没有指定类型参数时就兼容
// 泛型兼容性:只有类型参数T被接口成员使用时,才会影响兼容性
interface Empty<T> {}
let obj1: Empty<number> = {}
let obj2: Empty<string> = {}
obj1 = obj2

interface Empty1<T> {
    value: T
}
let obj3: Empty1<number> = {}
let obj4: Empty1<string> = {}
obj3 = obj4

//泛型函数:定义相同,未指定类型时兼容
let log1 = <T>(x: T): T => {
    console.log('x')
    return x
}
let log2 = <U>(y: U): U => {
    console.log('y')
    return y
}
log1 = log2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 4、类型保护

  • 含义:在特定的区块中保证变量属于某种确定的类型,可以在此区块中放心地引用此类型的属性,或者调用此类型的方法
  • 创建区块的方法
    • instanceof
    • typeof
    • in
    • 类型保护函数:特殊的返回值(arg is type 类型谓词)
enum Type {Strong, Week}

class Java {
    helloJava(){
        console.log('Hello Java')
    }
    java: string = 'java'
}

class JavaScript {
    helloJavaScript(){
        console.log('Hello JavaScript')
    }
}

//类型断言
function getLanguage(type: Type){
    let lang = type === Type.Strong ? new Java() : new JavaScript()
    if ((lang as Java).helloJava) {
        (lang as Java).helloJava()
    } else {
        (lang as JavaScript).helloJavaScript()
    }
    return lang
}
getLanguage(Type.Strong)

//使用类型保护
function getLanguage1(type: Type, x: string | number){
    let lang = type === Type.Strong ? new Java() : new JavaScript()
    //1.instanceof
    if (lang instanceof Java) {
        lang.helloJava()
    } else {
        lang.helloJavaScript()
    }

    //2.in
    if ('java' in lang){
        lang.helloJava()
    } else {
        lang.helloJavaScript()
    }

    //3.typeof
    if (typeof x === 'string'){
       console.log(x.length) 
    } else {
        x.toFixed(2)
    }
    return lang
}

//4. 类型保护函数:特殊的返回值(arg is type 类型谓词)
function isJava(lang: Java | JavaScript): lang is Java{
    return (lang as Java).helloJava !== undefined
}
function getLanguage2(type: Type, x: string | number){
    let lang = type === Type.Strong ? new Java() : new JavaScript()
    if (isJava(lang)){
        lang.helloJava()
    } else {
        lang.helloJavaScript()
    }
}
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

# 10、高级类型

ts为了保证语言的灵活性引入的一些高级特性,应变多变复杂的开发场景

# 1、交叉类型、联合类型

  • 交叉类型(类型并集)
    • 含义:将多个类型合并为一个类型,新的类型将具有所有类型的特性
    • 应用场景:混入
  • 联合类型(类型交集)
    • 含义:类型并不确定,可能为多个类型中的一个
    • 应用场景:多类型支持
    • 可区分的联合类型:结合联合类型和字面量类型的类型保护方法
    • 字面量类型
      • 字符串子面量
      • 数字字面量
      • 应用场景:限定变量取值范围
interface DogInterface {
    run(): void
}
interface CatInterface {
    jump(): void
}
//1. 交叉类型:用&符号连接
//需实现两个接口的方法
let pet: DogInterface & CatInterface = {
    run(){},
    jump(){}
};

//2. 联合类型:使用|,只能访问交集属性
//类型联合
let a1: number | string = 'a'
//字面量联合类型:字符串联合
let b1: 'a' | 'b' | 'c'
//字面量联合类型:数字联合
let c1: 1 | 2 | 3
//对象联合类型
class Dog1 implements DogInterface {
    run(){}
    eat(){}
}
class Cat1 implements CatInterface {
    jump(){}
    eat(){}
}
enum Master { Boy, Girl }
function getPet(master: Master){
    let pet = master === Master.Boy ? new Dog1() : new Cat1()
    //pet为联合类型 Dog1|Cat1,只能调用共有eat方法
    pet.eat()
    return pet
}

//3. 可区分的联合类型:结合联合类型和字面量类型的类型保护方法
interface Square {
    kind: "square",
    size: number
}
interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
interface Circle {
    kind: 'circle',
    r: number
}
type Shape = Square | Rectangle
//根据两种类型的公共属性建立类型保护机制
function area(s: Shape){
    switch (s.kind){
        case "square":
            return s.size * s.size
        case "rectangle":
            return s.height * s.width
    }
}
type Shape1 = Square | Rectangle | Circle
//添加分之保护检查:使用返回值number
function area1(s: Shape1): number{
    switch (s.kind){
        case "square":
            return s.size * s.size
        case "rectangle":
            return s.height * s.width
        case "circle":
            return Math.PI * s.r ** 2
    }
}
//添加分之保护检查:使用never类型
function area2(s: Shape1){
    switch (s.kind){
        case "square":
            return s.size * s.size
        case "rectangle":
            return s.height * s.width
        case "circle":
            return Math.PI * s.r ** 2
        //Argument of type 'Circle' is not assignable to parameter of type 'never'.
        //检查s是否为never类型
        //s为never类型,不会走到该分之
        //s不为never类型,前面分之有遗漏
        default:
            return ((e: never) => {throw new Error(e)})(s)
    }
}
console.log(area2({kind: 'circle', r: 1}))
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

# 2、索引类型

  • 几个重要概念:
    • keyof T(索引查询操作符):类型T公共属性名字的字面量联合类型
    • T[k](索引访问操作符):对象T的属性K所代表的类型
    • 泛型约束
  • 应用场景:从一个对象中选取某些属性的值
  • 索引类型能实现对对象的查询和访问,再配合泛型约束,建立对象、对象属性及属性值的约束关系
//索引类型
let obja = {
    a: 1,
    b: 2,
    c: 3
}

function getValues(obj: any, keys: string[]){
    return keys.map(key => obj[key])
}
console.log(getValues(obj, ['a', 'b'])) //['1', '2']
console.log(getValues(obj, ['e', 'f'])) //[undefined, undefined]


interface Obj {
    a: number,
    b: string
}
//keyof T: 查询索引操作
//let key: a | b
let key: keyof Obj //a | b

//T[k]: 索引访问操作符
//let value: number
let value: Obj['a']

// T extends U:泛型约束,继承某个类型获取某些属性

//类型约束改造
//索引类型能实现对对象的查询和访问,再配合泛型约束,建立对象、对象属性及属性值的约束关系
function getValues1<T, k extends keyof T>(obj: T, keys: k[]): T[k][]{
    return keys.map(key => obj[key])
}
//报错:console.log(getValues1(obj, ['e', 'f']))
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

# 3、映射类型

本质是预先定义的泛型接口,通常结合索引类型获取对象的属性或属性值,从而产生新类型

  • 含义:从旧类型创建出新类型
  • 应用场景:前三种为同态,只作用于T的属性
    • Readonly<T>:将T的所有属性变为只读
    • Partial<T>:将T的所有属性变为可选
    • Pick<T,K>:选取以K为属性的对象T的子集
    • Record<K,T>:创建属性为K的新对象,属性值的类型为T
interface Obja {
    a: string;
    b: number;
    c: boolean;
}
//1、同态:只会作用于T的属性,不会产生新的属性
//内置:只读属性
type ReadonlyObj = Readonly<Obja>
//内置类库:lib.es5.d.ts
//实现代码
/* type Readonly<T> = {
    //keyof T:索引的联合类型
    //p in 循环操作
    readonly [P in keyof T]: T[P];
}; */

//内置:可选属性
type PartialObj = Partial<Obja>
//实现代码
/* type Partial<T> = {
    [P in keyof T]?: T[P];
}; */

//内置:抽取子集
type PickObj = Pick<Obja, 'a' | 'b'>
/* 实现原理
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};
*/

// 2、非同态:产生新属性
type RecordObj = Record<'x' | 'y', Obja>
/*
type RecordObj = {
    x: Obja;
    y: Obja;
}
*/
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

# 4、条件类型

  • 含义:T extends U ? X : Y,如果类型T可以赋值给类型U,那么结果类型就是X,否则就是Y
  • 应用场景
    • Exclude<T,U>:从T中过滤掉可以赋值给U的类型
    • Extract<T,U>:从T中抽取出可以赋值为U的类型
    • NonNullable<T>:从T中除去undefined和null
    • ReturnType<T>:获取函数的返回值类型
// T extends U ? x: y
// T可以被赋值为类型U则为x,否则返回y

type TypeName<T> = 
    T extends string ? "string" :
    T extends number ? "number" :
    "object";

type T1 = TypeName<string> //string
type T2 = TypeName<string[]> //object

// 分布式条件类型
// ( A | B) extends U ? X : Y
// ( A extends U ? X : Y) | ( B extends U ? X : Y)
type T3 = TypeName<string | string[]> //"string" | "object"

type Diff<T, U> = T extends U ? never : T
type T4 = Diff<"a" | "b" | "c", "a" | "e"> // "b" | "c"
//Diff<"a", "a" | "e"> | Diff<"b", "a" | "e"> | Diff<"c", "a" | "e">
//never | "b" | "c"
//"b" | "c"

type NotNull<T> = Diff<T, undefined | null>
type T5 = NotNull<string | number | undefined | null> // string | number

type T6 = ReturnType<() => string> //string
//infer:待推断,延迟推断
//type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
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
Last Updated: 1/18/2022, 6:27:35 PM