• TypeScript(二)语法细节


    TypeScript

    联合类型

    TypeScript的类型系统允许我们使用多种运算符,从现有类型中构建新类型。
    联合类型是由两个或者多个其他类型组成的类型;
    表示可以是这些类型中的任何一个值;
    联合类型中的每一个类型被称之为联合成员(union’s members);

    let foo: number | string = 'abc';
    
    • 1

    传入给一个联合类型的值是非常简单的:只要保证是联合类型中的某一个类型的值即可
    但是我们拿到这个值之后,我们应该如何使用它呢?因为它可能是任何一种类型。
    比如我们拿到的值可能是string或者number,我们就不能对其调用string上的一些方法;

    我们需要使用缩小(narrow)联合(后续我们还会专门讲解缩小相关的功能);
    TypeScript可以根据我们缩小的代码结构,推断出更加具体的类型;

    function printID(id: number | string) {
    	console.log('您的id', id);
    
    	// 类型缩小
    	if (typeof id === 'string') {
    		console.log(id.length);
    	} else {
    		console.log(id);
    	}
    }
    
    printID('abc');
    printID(123);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    类型别名

    我们通过在类型注解中编写 对象类型 和 联合类型,但是当我们想要多次在其他地方使用时,就要编写多次。
    我们就可以给对象类型起一个别名:

    type MyNumber = number | string;
    
    function printID(id: MyNumber) {
    	console.log(id);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在前面我们通过type可以用来声明一个对象类型:

    type PointType = {
    	x: number;
    	y: number;
    	z?: number;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    对象的另外一种声明方式就是通过接口来声明:

    interface PointType2 {
    	x: number;
    	y: number;
    	z?: number;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    interface和type区别

    如果是定义非对象类型,通常推荐使用type,比如Direction、Alignment、一些Function;

    如果是定义对象类型,那么他们是有区别的:
    interface 可以重复的对某个接口来定义属性和方法;
    而type定义的是别名,别名是不能重复的;

    type PointType1 = {
    	x:number,
    	y:number,
    
    }
    type PointType1 = {
    	z?:number
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    interface PointType {
    	x: number;
    	y: number;
    }
    interface PointType {
    	z: number;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    interface可以为现有的接口提供更多的扩展

    interface IPerson {
    	name: string;
    	age: number;
    }
    
    interface Ming extends IPerson {
    	NickName: string;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    interface可以被类实现

    class Person implements IPerson {
    
    }
    
    • 1
    • 2
    • 3

    交叉类型

    交叉类似表示需要满足多个类型的条件;
    交叉类型使用 & 符号;
    当我们对number和string交叉时 没有一个同时满足既是number又是string的值 所以是never

    type MyType = string & number;
    
    • 1

    在这里插入图片描述

    我们进行交叉时,通常是对对象类型进行交叉的:

    interface IKun {
    	name: string;
    	age: number;
    }
    
    interface ICode {
    	name: string;
    	coding: () => void;
    }
    
    type InfoType = IKun & ICode;
    
    const info: InfoType = {
    	name: 'haha',
    	age: 18,
    	coding: function () {
    		console.log('coding');
    	},
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    类型断言as

    有时候TypeScript无法获取具体的类型信息,这个我们需要使用类型断言(Type Assertions)。
    比如我们通过 document.getElementById,TypeScript只知道该函数会返回 HTMLElement ,但并不知道它具体的类型:

    const imgEl = document.querySelector('.img') as HTMLImageElement;
    
    if (imgEl !== null) {
    	imgEl.src = 'xxx';
    	imgEl.alt = 'yyy';
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    TypeScript只允许类型断言转换为 更具体 或者 不太具体 的类型版本,此规则可防止不可能的强制转换

    const name = '111' as number
    
    • 1

    在这里插入图片描述

    const name = ('111' as unknown ) as number
    
    • 1

    非空类型断言!

    当我们编写下面的代码时,在执行ts的编译阶段会报错:
    这是因为传入的message有可能是为undefined的,这个时候是不能执行方法的;

    function printMessage(message?: string){
    	console.log(message.toUpperCase())
    }
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    但是,我们确定传入的参数是有值的,这个时候我们可以使用非空类型断言:
    非空断言使用的是 ! ,表示可以确定某个标识符是有值的,跳过ts在编译阶段对它的检测;

    function printMessage(message?: string){
    	console.log(message!.toUpperCase())
    }
    
    • 1
    • 2
    • 3

    字面量类型

    将多个字面量类型联合起来

    type Direction = 'left' | 'right' | 'up' | 'down';
    const d1: Direction = 'left';
    
    • 1
    • 2

    字面量推理

    当我们封装一个请求方法

    type MethodType = 'get' | 'post';
    function request(url: string, method: MethodType) {}
    request('url', 'get');
    
    • 1
    • 2
    • 3

    下面的做法是错误的 info.method获取的是string类型

    const info = {
    	url: 'xxx',
    	method: 'post',
    };
    request(info.url, info.method);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    解决方案一

    const info = {
    	url: 'xxx',
    	method: 'post',
    };
    
    • 1
    • 2
    • 3
    • 4

    解决方案二 直接让info对象类型是一个字面量类型

    const info: { url: string; method: 'post' } = {
    	url: 'xxx',
    	method: 'post',
    };
    request(info.url, info.method);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    解决方案三 使用const 变成一个字面量

    const info = {
    	url: 'xxx',
    	method: 'post',
    } as const;
    
    request(info.url, info.method);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    类型缩小

    常见的类型保护有如下几种
    typeof
    平等缩小(比如=== !==)
    instanceof
    in

    typeof

    在 TypeScript 中,检查返回的值typeof是一种类型保护:
    因为 TypeScript 对如何typeof操作不同的值进行编码。

    function printID(id: number | string) {
    	if (typeof id === 'string') {
    		console.log(id.length);
    	} else {
    		console.log(id);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    平等缩小

    我们可以使用Switch或者相等的一些运算符来表达相等性(比如=== !== == and != ):

    type Direction = 'left' | 'right' | 'up' | 'down';
    
    function switchDirection(direction: Direction) {
    	switch (direction) {
    		case 'left':
    			console.log('调用left方法');
    			break;
    		case 'right':
    			console.log('调用right方法');
    			break;
    		case 'up':
    			console.log('调用up方法');
    			break;
    		case 'down':
    			console.log('调用down方法');
    		default:
    			console.log('调用默认方法');
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    instanceof

    JavaScript 有一个运算符来检查一个值是否是另一个值的“实例”

    function printDate(date: string | Date) {
    	if (date instanceof Date) {
    		console.log(date.getTime());
    	} else {
    		console.log(date);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    in操作符

    Javascript 有一个运算符,用于确定对象是否具有带名称的属性:in运算符
    如果指定的属性在指定的对象或其原型链中,则in 运算符返回true;

    interface ISwim {
    	swim: () => void;
    }
    
    interface IRun {
    	run: () => void;
    }
    
    function move(animal: ISwim | IRun) {
    	if ('swim' in animal) {
    		animal.swim();
    	} else {
    		animal.run();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    TypeScript函数类型

    type CalcFunc = (num1:number, num2:number) => void
    
    function calc(fn: CalcFunc){
    	console.log(fn(20, 30))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在上面的语法中 (num1: number, num2: number) => void,代表的就是一个函数类型:
    接收两个参数的函数:num1和num2,并且都是number类型;
    并且这个函数是没有返回值的,所以是void;

    ts对于传入的参数类型的参数个数不进行检测(校验)
    下面的语法是不会报错的

    type CalcType = (num1: number, num2: number) => number;
    
    function calc(calcFn: CalcType) {
    	calcFn(10, 20);
    }
    
    calc(function nu(m1) {
    	return 123;
    });
    ```
    我们也可以使用forEach来帮助我们理解
    对于forEach来说 内部定义了value, index, array为必传项 但是我们不传也不会报错,就是这个道理
    所以我们自己定义函数类型时,参数可以都设置为必传的
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/ad18bf05a3144a9b9e82d507ad86ea68.png)
    
    
    注意:在某些语言中,可能参数名称num1和num2是可以省略,但是TypeScript是不可以的:
    
    ## 调用签名
    在 JavaScript 中,函数除了可以被调用,自己也是可以有属性值的。
    然而前面讲到的函数类型表达式并不能支持声明属性;
    如果我们想描述一个带有属性的函数,我们可以在一个对象类型中写一个调用签名(call signature);
    ```typescript
    interface IBar {
    	name: string;
    	age: number;
    	// 函数是可以调用:函数调用签名
    	// (参数列表):number
    	(num1: number): number;
    }
    
    const bar: IBar = (num1: number): number => {
    	return 123;
    };
    
    bar.name = 'aaa';
    bar.age = 18;
    bar(123);
    ```
    注意这个语法跟函数类型表达式稍有不同,在参数列表和返回的类型之间用的是 : 而不是 =>。
    
    1.如果只是描述函数类型本身(函数可以被调用) 使用函数类型表达式(Function Type Expressions)
    2.如果在描述函数作为对象可调用 同时也有其他属性使 使用函数调用签名(Call Signatures)
    
    ## 调用签名
    JavaScript 函数也可以使用 new 操作符调用,当被调用的时候,TypeScript 会认为这是一个构造函数(constructors),因为
    他们会产生一个新对象。
    你可以写一个构造签名( Construct Signatures ),方法是在调用签名前面加一个 new 关键词;
    ```typescript
    class Person {}
    
    interface ICTORPerson {
    	new (): Person;
    }
    
    function factory(fn: ICTORPerson) {
    	const f = new fn();
    	return f;
    }
    
    factory(Person);
    ```
    
    ## 构造签名
    JavaScript 函数也可以使用 new 操作符调用,当被调用的时候,TypeScript 会认为这是一个构造函数(constructors),因为
    他们会产生一个新对象
    ```typescript
    class Person {}
    
    interface ICTORPerson {
    	new (): Person;
    }
    
    function factory(fn: ICTORPerson) {
    	const f = new fn();
    	return f;
    }
    
    factory(Person);
    ```
    
    ## 参数的可选类型
    我们可以指定某个参数是可选的:
    ```typescript
    function foo(x: number, y?: number) {
    	if (y !== undefined) {
    		console.log(y + 10);
    	}
    }
    ```
    可选参数的类型是什么 undefined | number 联合类型
    另外可选类型需要在必传参数的后面
    
    ## 默认参数
    有默认值的情况下 参数的类型主句可以省略
    有默认值的参数 是可以接收一个undefined的值
    ```typescript
    function foo(x: number, y = 100) {
    	console.log(y + 10);
    }
    foo(10);
    foo(10, undefined);
    foo(10, 55);
    ```
    
    ## 剩余参数
    从ES6开始,JavaScript也支持剩余参数,剩余参数语法允许我们将一个不定数量的参数放到一个数组中。
    ```typescript
    function sum(...nums: number[]) {
    	let total = 0
    	for (const num of nums) {
    		total += num
    	}
    	return total
    }
    
    const result1 = sum(10, 20, 30)
    console.log(result1)
    
    const result2 = sum(10, 20, 30, 40)
    console.log(result2)
    ```
    
    ## 函数的重载
    在TypeScript中,如果我们编写了一个add函数,希望可以对字符串和数字类型进行相加,应该如何编写呢?
    下面的做法是错误的 联合类型是做不到的
    ```typescript
    function add(arg1: mumber | string, arg2: mumber | string) {
    	return arg1 + arg2;
    }
    ```
    在TypeScript中,我们可以去编写不同的重载签名(overload signatures)来表示函数可以以不同的方式进行调用;
    一般是编写两个或者以上的重载签名,再去编写一个通用的函数以及实现;
    
    ### sum函数的重载
    在我们调用sum的时候,它会根据我们传入的参数类型来决定执行函数体时,到底执行哪一个函数的重载签名;
    ```typescript
    function add(arg1: number, arg2: number): number;
    function add(arg1: string, arg2: string): string;
    function add(arg1: any, arg2: any): any {
    	return arg1 + arg2;
    }
    
    add(10, 20);
    add('aaa', 'bbb');
    ```
    但是注意,有实现体的函数,是不能直接被调用的
    ```typescript
    sum({name: 'aaa'}, {age: 18})
    ```
    
    ## 联合类型和重载
    我们现在有一个需求:定义一个函数,可以传入字符串或者数组,获取它们的长度。
    这里有两种实现方案:
    方案一:使用联合类型来实现;
    ```typescript
    function getLength(arg: string | any[]) {
    	return arg.length;
    }
    ```
    方案二:实现函数重载来实现;
    
    ```typescript
    function getLength(arg: string): number;
    function getLength(arg: any[]): number;
    function getLength(arg){
    	return arg.length;
    }
    ```
    在可能的情况下,尽量选择使用联合类型来实现
    
    ## 可推导的this类型
    TypeScript是如何处理this呢?我们先来看两个例子
    ```typescript
    const obj = {
    	name: 'obj',
    	foo: function() {
    		console.log(this.name)
    	}
    }
    
    obj.foo()
    ```
    ```typescript
    function foo1() {
    	console.log(this)
    }
    foo1()
    ```
    上面的代码默认情况下是可以正常运行的,也就是TypeScript在编译时,认为我们的this是可以正确去使用的
    这是因为在没有指定this的情况,this默认情况下是any类型的
    
    VSCode在检测我们的TypeScript代码时,默认情况下运行不确定的this按照any类型去使用
    但是我们可以创建一个tsconfig.json文件,并且在其中告知VSCodethis必须明确执行(不能是隐式的)
    
    在设置了noImplicitThis为true时, TypeScript会根据上下文推导this,但是在不能正确推导时,就会报错,需要我们明确
    的指定this。
    
    ## 指定this的类型
    在开启noImplicitThis的情况下,我们必须指定this的类型
    
    如何指定呢?函数的第一个参数类型:
    函数的第一个参数我们可以根据该函数之后被调用的情况,用于声明this的类型(名词必须叫this);
    在后续调用函数传入参数时,从第二个参数开始传递的,this参数会在编译后被抹除;
    ```typescript
    function foo(this: { name: string }, info: { name: string }) {
    	console.log(this, info);
    }
    foo.call({ name: 'james' }, { name: 'kobe' });
    
    ```
    
    ## this相关的内置工具
    Typescript 提供了一些工具类型来辅助进行常见的类型转换,这些类型全局可用。
    ThisParameterType:
    用于提取一个函数类型Type的this (opens new window)参数类型;
    如果这个函数类型没有this参数返回unknown
    ```typescript
    function foo(this: { name: string }, info: { name: string }) {
    	console.log(this, info);
    }
    type FooType = typeof foo;
    //1.获取FooType类型中this的类型
    type FooThisType = ThisParameterType<FooType>;
    
    ```
    OmitThisParameter:
    用于移除一个函数类型Type的this参数类型, 并且返回当前的函数类型
    ```typescript
    function foo(this: { name: string }, info: { name: string }) {
    	console.log(this, info);
    }
    type FooType = typeof foo;
    
    //2.Omit 去除this参数类型 剩余的函数类型
    type PureFooType = OmitThisParameter<FooType>;
    
    ```
    ThisType 用于绑定一个上下文的this
    这个类型不返回一个转换过的类型,它被用作标记一个上下文的this类型。(官方文档)
    事实上官方文档的不管是解释,还是案例都没有说明出来ThisType类型的作用
    
    
    案例
    ```typescript
    interface IState {
    	name: string;
    	age: number;
    }
    interface IStore {
    	state: IState;
    	eating: () => void;
    	running: () => void;
    }
    const store: IStore & ThisType<IState> = {
    	state: {
    		name: 'why',
    		age: 18,
    	},
    	eating: function () {
    		console.log(this.name);
    	},
    	running: function () {
    		console.log(this.name);
    	},
    };
    store.eating.call(store.state);
    
    ```
    
    • 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
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
  • 相关阅读:
    azure devops工具实践分析
    【C++】-- 红黑树详解
    皕杰报表中填报控件显示模式控制问题
    《计算机网络基础》期中考试试卷
    【大数据】Hadoop三大核心组件入门
    Java中的线程到底有哪些安全策略
    在线问诊 Python、FastAPI、Neo4j — 创建 疾病节点
    高通开发系列 - ALSA声卡驱动中音频通路kcontrol控件
    知识图谱1_2——下载neo4j客户端
    深度学习之NLP
  • 原文地址:https://blog.csdn.net/weixin_65402230/article/details/127967610