TS基础

原始数据类型

:meat_on_bone: JS中数据类型分两类:原始数据类型和引用类型。原始数据类型包括布尔值、数值、字符串、null、undefined和es6中的新类型Symbol、BigInt。

布尔值的使用

1
let isDone: boolean = false;

模板字符串的使用

1
2
3
4
5
6
let myName: string = 'Tom';
let myAge: number = 25;

// 模板字符串
let sentence: string = `Hello, my name is ${myName}.
I'll be ${myAge + 1} years old next month.`;

:zero: 空值,当函数没有返回值时使用

1
2
3
function alertName(): void {
alert('My name is Tom');
}

任意值

允许被赋值为任意类型,可以访问任意值的任何属性和任何方法。可以认为对任意值的任何操作,返回的类型都是任意值。

如果声明时没有指定其类型,会被识别为任意值类型。

类型推论

当定义时没有明确指定类型时,TS会根据后面的赋值自动推断出一个类型。如果定义时没有赋值,会被推断成any

联合类型

表示取值可以为多种类型的一个

1
2
3
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

当访问联合类型的属性或方法时,注意只能访问它们共有的属性或方法。

当被赋值的时候,会根据类型推论判断出一个类型。

接口

接口是对行为的抽象,也用于对对象的形状进行描述。对象的形状必须与接口的形状一致,接口一般首字母大写,有的会加上I

可选、任意、只读属性

可选属性可以不一定完全匹配接口的形状,任意属性指属性的类型可以是确定属性或可选属性中的某一个,只读属性用readonly定义,只能在创建的时候被赋值。:mushroom:

1
2
3
4
5
6
7
8
9
10
interface Person {
name: string;
age?: number;//可选属性
[propName: string]: string | number;//任意属性
readonly id: number;//只读属性
}

let tom: Person = {
name: 'Tom'
};

数组的类型

TS中数组有多种定义方式。

类型+方括号[]

1
let fibonacci: number[] = [1, 1, 2, 3, 5];

数组的项中不允许出现其他的类型,包括调用方法添加项,比如说push方法只能添加number类型

数组泛型

使用Array<elemType> 来表示数组

1
let fibonacci: Array<number> = [1, 1, 2, 3, 5];

接口表示

1
2
3
4
interface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];

可以描述数组,但一般不会这么做,比较复杂。但可以用在描述类数组中。

类数组

比如说arguments,应该这样定义

1
2
3
4
5
6
7
function sum() {
let args: {
[index: number]: number;
length: number;
callee: Function;
} = arguments;
}

事实上常用的类数组都有自己的接口定义,如 IArguments, NodeList, HTMLCollection

其中 IArguments 是 TypeScript 中定义好了的类型,它实际上是一个内置对象👇

1
2
3
4
5
interface IArguments {
[index: number]: any;
length: number;
callee: Function;
}

any在数组中

用any表示数组中允许出现任意类型

1
let list:any[]=['qingcheng',12,{name:'xumeijin'}]

函数的类型

JS中定义函数的方法有两种,函数声明,函数表达式,在TS中要对函数的输入输出进行约束

函数声明

1
2
3
function sum(x: number, y: number): number {
return x + y;
}

函数表达式

1
2
3
let mySum = function (x: number, y: number): number {
return x + y;
};

这样写,左边是根据类型推论推断出来的。完整写法如下👇

1
2
3
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
};

注意这里的=>表示函数的定义,不是es6中的箭头函数

1
2
3
4
5
6
7
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1;
}

可选参数

与前面的可选属性类似,用?表示,需要注意可选参数必须在必需参数后面。

参数默认值

TS将添加了默认值的参数识别为可选参数,此时不受可选参数必须在必需参数后面的约束 :happy:

1
2
3
4
5
function buildName(firstName: string = 'Tom', lastName: string) {
return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let cat = buildName(undefined, 'Cat');

ES6也可以设置函数默认参数

剩余参数

ES6中可以用…rest获取剩余参数,TS也支持。:surfing_woman:

1
2
3
4
5
6
7
8
function push(array: any[], ...items: any[]) {
items.forEach(function(item) {
array.push(item);
});
}

let a = [];
push(a, 1, 2, 3);

注意rest参数只能是最后一个参数

重载

允许一个函数接受不同数量或类型的参数时,走不同的逻辑。利用联合类型,可以这样实现👇

1
2
3
4
5
6
7
function reverse(x: number | string): number | string {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}

还可以更精确地表示,让输出与输入的类型一致 :star2:

1
2
3
4
5
6
7
8
9
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}

类型断言

用于手动指定一个值的类型:crossed_fingers:,语法如下

1
as 类型 或  <类型>值

在tsx中必须用前者,由于形如 <Foo> 的语法在 tsx 中表示的是一个 ReactNode,在TS中也可以表示为一个泛型。所以建议使用前者。

类型断言常见类型如下

对联合类型断言

之前提到过,当不确定联合类型的类型时候,只能访问公共的属性或方法。但是当我们不确定类型也想访问里面的方法咋办。

这时可以用到断言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}

function isFish(animal: Cat | Fish) {
if (typeof (animal as Fish).swim === 'function') {
return true;
}
return false;
}

但是断言只能影响TS的编译,如果逻辑错了运行时会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}

function swim(animal: Cat | Fish) {
(animal as Fish).swim();//animal为Cat的时候,是没有swim方法的
}

const tom: Cat = {
name: 'Tom',
run() { console.log('run') }
};
swim(tom);
// Uncaught TypeError: animal.swim is not a function`

所以尽量避免断言后调用方法或引用深层属性,以减少不必要的运行时错误🔒

父类断言为具体子类

当类之间有继承关系时,通过断言父类来判断子类类型

1
2
3
4
5
6
7
8
9
10
11
12
13
class ApiError extends Error {
code: number = 0;
}
class HttpError extends Error {
statusCode: number = 200;
}

function isApiError(error: Error) {
if (typeof (error as ApiError).code === 'number') {
return true;
}
return false;
}

以上的例子也可以用instanceof来判断,因为 ApiError 是一个 JavaScript 的类,但是当ApiError 为一个接口时,使用typeof更加合适。

对任何类型断言为any

当我们非常确定这段代码不会出错,比如在window下添加foo属性

1
window.foo = 1;

直接这么写 TypeScript 编译时会报错,提示我们 window 上不存在 foo 属性。所以需要使用断言

1
(window as any).foo = 1;

TypeScript 的设计理念之一:一方面不能滥用 as any,另一方面也不要完全否定它的作用,我们需要在类型的严格性和开发的便利性之间掌握平衡 :balance_scale:

内置对象

JS本身就有很多内置对象,在TS中可以当作已经定义好了的类型

ECMAScript标准里的内置对象:Boolean、Error、Date、RegExp

DOM,BOM中的内置对象:Document、HTMlElement、Event、NodeList

可以将变量定义为这些类型。

Node.js 不是内置对象的一部分,如果想用 TypeScript 写 Node.js,则需要引入第三方声明文件:

1
npm install @types/node --save-dev
查看评论