TypeScript(二)语法细节

TypeScript

联合类型

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

let foo: number | string = 'abc';

传入给一个联合类型的值是非常简单的:只要保证是联合类型中的某一个类型的值即可
但是我们拿到这个值之后,我们应该如何使用它呢?因为它可能是任何一种类型。
比如我们拿到的值可能是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);

类型别名

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

type MyNumber = number | string;

function printID(id: MyNumber) {
	console.log(id);
}

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

type PointType = {
	x: number;
	y: number;
	z?: number;
};

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

interface PointType2 {
	x: number;
	y: number;
	z?: number;
}

interface和type区别

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

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

type PointType1 = {
	x:number,
	y:number,

}
type PointType1 = {
	z?:number
}

TypeScript(二)语法细节_第1张图片

interface PointType {
	x: number;
	y: number;
}
interface PointType {
	z: number;
}

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

interface IPerson {
	name: string;
	age: number;
}

interface Ming extends IPerson {
	NickName: string;
}

interface可以被类实现

class Person implements IPerson {

}

交叉类型

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

type MyType = string & number;

在这里插入图片描述

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

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

类型断言as

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

const imgEl = document.querySelector('.img') as HTMLImageElement;

if (imgEl !== null) {
	imgEl.src = 'xxx';
	imgEl.alt = 'yyy';
}

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

const name = '111' as number

在这里插入图片描述

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

非空类型断言!

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

function printMessage(message?: string){
	console.log(message.toUpperCase())
}

TypeScript(二)语法细节_第2张图片

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

function printMessage(message?: string){
	console.log(message!.toUpperCase())
}

字面量类型

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

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

字面量推理

当我们封装一个请求方法

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

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

const info = {
	url: 'xxx',
	method: 'post',
};
request(info.url, info.method);

在这里插入图片描述

解决方案一

const info = {
	url: 'xxx',
	method: 'post',
};

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

const info: { url: string; method: 'post' } = {
	url: 'xxx',
	method: 'post',
};
request(info.url, info.method);

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

const info = {
	url: 'xxx',
	method: 'post',
} as const;

request(info.url, info.method);

类型缩小

常见的类型保护有如下几种
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);
	}
}

平等缩小

我们可以使用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('调用默认方法');
	}
}

instanceof

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

function printDate(date: string | Date) {
	if (date instanceof Date) {
		console.log(date.getTime());
	} else {
		console.log(date);
	}

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

TypeScript函数类型

type CalcFunc = (num1:number, num2:number) => void

function calc(fn: CalcFunc){
	console.log(fn(20, 30))
}

在上面的语法中 (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);

```

你可能感兴趣的:(TypeScript,typescript,javascript,前端)