TypeScript学习

TypeScript 的发展已经深入到前端社区的方方面面了,任何规模的项目都或多或少得到了 TypeScript 的支持。同时vue3在经过vue2之后的发展过程中采用了TypeScript进行了重构,也让使用vue作为主力开发框架的开发者们,需要学习TypeScript来适应日常的开发过程。

同时也有赖于TypeScript这门语言的优点:

  1. 完全兼容于JavaScript
  2. 适用于任何规模

使得我们的开发的代码能够更加严谨和健壮!

学习内容及教程推荐

  1. TypeScript入门教程 中文文档,很容易阅读和理解,对于新手十分友好
  2. typescript中文官网–手书 英文文档,阅读体验对国人不是很友好
  3. typescript中文文档教程 当前最新typescript版本为 “typescript”: “^4.3.2”,文档最新版本只更新到v3.1版本,是有滞后的。

除了实现 ECMAScript 标准之外,TypeScript 团队也推进了诸多语法提案

可选链操作符(?.)

  参考链接: https://zh.javascript.info/optional-chaining

空值合并操作符

  参考链接: https://zh.javascript.info/nullish-coalescing-operator

throw表达式

  throw 语句抛出一个错误。

当错误发生时, JavaScript 会停止执行并抛出错误信息。

描述这种情况的技术术语是:JavaScript 将抛出一个错误。

throw 语句创建自定义错误。

技术术语是: 抛出异常。

异常可以是 JavaScript 字符串、数字、逻辑值或对象:

正则匹配索引

typescript的安装

使用命令行工具

1
npm install -g typescript

hello.ts

1.新建一个简单函数:hello.ts

1
2
3
4
5
6
  function sayHello(person: string) {
return 'hello,'+ person;

}
let user = 'tom';
console.log(sayHello(user));

2.安装好typescript后,在命令行终端键入以下命令: tsc hello.ts

即可看到显示的hello.js文件

hello.ts
hello.js

TypeScript 只会在编译时对类型进行静态检查,如果发现有错误,编译的时候就会报错。而在运行时,与普通的 JavaScript 文件一样,不会对类型进行检查。

简单来说,typescript主要是在编译过程中检查错误的存在,在没有进行其他设置的情况下,即使错误,也会继续生成相应的js文件,当然这对于使用typescript的用户来说是需要避免的,开发着需要在ts编译阶段报错时终止(ts文件→js文件)这一过程,需要进行以下配置

可以在 tsconfig.json 中配置 noEmitOnError 即可。关于 tsconfig.json,参考思否教程

typescript基础

原始数据类型

原始数据类型现今总共有: 7种 = 5种 + 2种
5种: 布尔值,数值,字符串,null, undefined
2种: Symbol (ES6新类型); BigInt(ES10新类型)

布尔值

1
let isDone: boolean = false;

很明显可以正常编译,那么采用构造函数的形式呢?

1
let createdByNewBoolean: boolean = new Boolean(1);

代码预览—布尔值

error TS2322: Type ‘Boolean’ is not assignable to type ‘boolean’.
‘boolean’ is a primitive, but ‘Boolean’ is a wrapper object. Prefer using ‘boolean’ when possible.

Boolean 是对应布尔值的引用类型。要创建一个 Boolean 对象,就使用 Boolean 构造函数并传入
true 或 false

即通过构造函数new Boolean(1)的结果实际是布尔值对象,而不是布尔值,所以会出现类型错误,理解原始布尔值和 Boolean 对象之间的区别非常重要,强烈建议永远不要使用后者.

1
let createdByNewBoolean: Boolean = new Boolean(1);

代码预览—布尔对象

数字

1
2
3
4
5
6
7
8
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
// ES6 中的二进制表示法
let binaryLiteral: number = 0b1010;
// ES6 中的八进制表示法
let octalLiteral: number = 0o744;
let notANumber: number = NaN;
...

代码预览—数字

void \ null \ undfined

在typescript中没有空值的概念,遇到这种情况可以采用void来标识没有返回值的函数   (划重点:没有返回值的函数)
1
2
3
    function alertName(): void {
alert('My name is Tom');
}
声明一个void类型的变量是没有什么用处的,因为之后你只能将它赋值为null或者undfined(只在 --strictNullChecks 未指定时):
注意是变量,对于没有返回值的函数还是有必要的

undefined 和 null 两个空类型的设计,使用上不方便,所以 通过strictNullChecks严格校验类型,让代码更安全

1
2
3
4
5
6
7
8
9
10
11
12
  // 两个空类型
let u: undefined = undefined
let n: null = null

// 常见区别
Number(null) // 0
Number(undefined) // NaN

let age: number = null
console.log(5 + age) //5

age = und...

空值校验预览

注意:
1.与 void 的区别是,undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 number 类型的变量:
2.而 void 类型的变量不能赋值给 number 类型的变量:

any

如果是一个普通类型,在赋值过程中改变类型是不被允许的:但如果是 any 类型,则允许被赋值为任意类型。在任意值上访问任何属性都是允许的:也允许调用任何方法:变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型

类型推论

  • 类型推论: 字面意思即使,当类型没有被明确指定的时候,那么 TypeScript 会依照类* 型推论(Type Inference)的规则推断出一个类型。
  • 如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查。

联合类型

  • 即在定义类型时可以拥有多种类型,联合类型使用 | 分隔每个类型。
  • 当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:
1
2
3
4
5
6
7
function getLength(something: string | number): number {
return something.length;
}

// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.

当访问的不是共有属性或者方法的时候,就会报错。联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型:

1
2
3
4
5
6
7
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
console.log(myFavoriteNumber.length); // 5
myFavoriteNumber = 7;
console.log(myFavoriteNumber.length); // 编译时报错

// index.ts(5,30): error TS2339: Property 'length' does not exist on type 'number'.

上例中,第二行的 myFavoriteNumber 被推断成了 string,访问它的 length 属性不会报错。而第四行的 myFavoriteNumber 被推断成了 number,访问它的 length 属性时就报错了。

接口 (给对象准备的自定义类型)

实例代码:

1
2
3
4
5
6
7
8
9
interface Person {
name: string;
age: number;
}

let tom: Person = {
name: 'Tom',
age: 25
};

形状得一样

赋值的时候需要保证接口的形状必须和其指定的类型一样。

可选属性(形状可以不一样,得加条件)

当然在开发需求中也会有需要其中某一个或者某几个,不要限制的那么死板,于是就有了 可选属性,通过在“:”前添加一个“?”来表示属性的可选性,这样也可以使得限制变得宽松,形状也可以有不一致。

可选属性可以实现已定义属性的有或无,但是对于未定义的属性,则产生了限制,因为它有严格的形状限制,而有时候我们希望一个接口允许有任意的属性,可以使用如下方式:

1
2
3
4
5
6
7
8
9
10
interface Person {
name: string;
age?: number;
[propName: string]: any;
}

let tom: Person = {
name: 'Tom',
gender: 'male'
};

一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:

在上面的例子中,我们将任意属性的类型指定为any,则name指定的字符串类型和age指定的数字类型都是any类型的子集,所以是符合要求的。

下面来测试一个不属于其子集的例子:

1
2
3
4
5
6
7
8
9
10
11
interface Person {
name: string;
age?: number;
[propName: string]: string;
}

let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};

非其子集*–预览

只读属性(readonly)

在一些情况下,我们需要值是只读属性,当它被赋值一次以后,不能再对其进行赋值,此时可以采用readonly属性来指定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}

let tom: Person = {
id: 89757,
name: 'Tom',
gender: 'male'
};

tom.id = 9527;

// index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.

使用readonly属性的时候,需要在定义的时候即赋值,在其拥有readonly属性之后再通过对象操作的方式进行赋值也会报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}

let tom: Person = {
name: 'Tom',
gender: 'male'
};

tom.id = 89757;

// index.ts(8,5): error TS2322: Type '{ name: string; gender: string; }' is not assignable to type 'Person'.
// Property 'id' is missing in type '{ name: string; gender: string; }'.
// index.ts(13,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.

数组类型

数组是日常开发中遇到的最多的数据结构之一,从它的基本方法的多样性也可以看出它的使用度是极高的。那么在typescript世界中对于数组类型的定义又会有哪些实用方式呢?

类型+方括号 表示法

数组字面量是非常普遍的数组定义方法,在typescript中则采用了 类型+方括号的组成,一方面能够非常方便得表示数组,另一方面可以限制数组内元素得类型。

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

1.数组的项中不允许出现其他的类型,且数组的一些方法也会根据数组在定义时候的类型加以限制。

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

// Type 'string' is not assignable to type 'number'.

1
2
3
4
5
let fibonacci: number[] = [1, 1, 2, 3, 5];
fibonacci.push('8');

// Argument of type '"8"' is not assignable to parameter of type 'number'.

定义一个任意类型的数组:

1
let list: any[] = ['xcatliu', 25, { website: 'http://xcatliu.com' }];

泛型

我们也可以使用数组泛型(Array Generic) Array<类型> 来表示数组:

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];

结合数组的特点,首先索引值必须是数字,在上述的例子中规定了属性值也为数字,所以可以将[1,1,2,3,5]赋值给fibonacci.

与前两种比较而言,此种方法是比较复杂的,当然它的主要用处是用来表示类数组

类数组

类数组的概念

想要理解为什么多用比较复杂的接口来定义类数组? 首先需要了解类数组的基本概念。

类数组不是数组,它的原型是对象

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

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

1
2
3
4
function sum() {
let args: IArguments = arguments;
}

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

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

函数的类型

前面提到过可以用void来表示没有返回值的函数类型,这一节,我们将学习到对于有返回值的函数应该怎么表示,函数作为编程语言里不可缺少的部分,能够利用其灵活性,给我们的代码开发过程中带来效率的提升。首先,需要重温一下原生JavaScript中对于函数的声明方式:函数声明(Function Declaration)和函数表达式(Function Expression):

1
2
3
4
5
6
7
8
9
// 函数声明(Function Declaration)
function sum(x, y) {
return x + y;
}

// 函数表达式(Function Expression)
let mySum = function (x, y) {
return x + y;
};

一个函数有输入和输出,要在typescript中对其进行约束,需要把输入和输出都考虑到

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

注意:输入多余的(或者少于要求的)参数,是不被允许的:

  • 多输入参数:
    1
    2
    3
    4
    5
    6
    function sum(x: number, y: number): number {
    return x + y;
    }
    sum(1, 2, 3);

    // index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.
  • 少输入参数:
    1
    2
    3
    4
    5
    6
    function sum(x: number, y: number): number {
    return x + y;
    }
    sum(1);

    // index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.

函数表达式的类型加注

函数表达式相较于函数声明式是比较复杂的,首先是因为在赋值表达式两边都有字符,实际上如果在函数表达式左侧不对类型加以约束,在编译过程是可以通过的。 为了严谨,需要在函数左侧也加以类型约束。 通过类似于ES6中的箭头函数的样式,来表示函数左侧的样式,当然其本质还是不同的,在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。

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

当然为了代码的可读性,也可以采用接口的方式定义函数

1
2
3
4
5
6
7
8
9
interface SearchFunc {
(source: string,subString: string): boolean;
}

let mySrarch: SearchFunc;

mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1;
}

难点:用接口定义函数的理解
怎么理解用typescript接口定义函数
typescript定义函数类型

个人理解:在定义接口的时候,因为是应用于函数的接口定义,所以左侧是函数的参数类型,通过冒号连接右侧的,实际是表示函数的类型的值,相当于用 “=>” 在函数表达式中的实现方式,应该是一种约定的方式。

前文提到:在没有使用可选参数的情况下,对于函数而言,在输入端多输入参数或者是少输入参数都会产生错误,在使用可选参数时,可以放松一些对于函数输入的要求,但是也有限制。

1
2
3
4
5
6
7
8
9
function buildName(firstName: string, lastName?: string) {
if (lastName) {
return firstName + ' ' + lastName;
} else {
return firstName;
}
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');

可选参数后面不允许再出现必需参数了:

1
2
3
4
5
6
7
8
9
10
11
function buildName(firstName?: string, lastName: string) {
if (firstName) {
return firstName + ' ' + lastName;
} else {
return lastName;
}
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName(undefined, 'Tom');

// index.ts(1,40): error TS1016: A required parameter cannot follow an optional parameter.

参数默认值

在ES6中允许给函数的参数设置默认值,在typescript会将具有默认值的参数识别为可选参数,

1
2
3
4
5
6
7
function buildName(firstName: string, lastName: string = 'Cat') {
return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
console.log(tomcat);
let tom = buildName('Tom');
...

可选参数在后

此时就不受「可选参数必须接在必需参数后面」的限制了:

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

可选参数在前

剩余参数

剩余参数,rest后面不能再有其他参数了,表示除了之前的参数外的其余的所有参数。ES6-剩余参数

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

let a:any[]= []; // 在这里,a元素在被赋值为[]时,隐式的具有any[]类型.
push(a, 1, 2, 3);
console.log(a)

它定义了一个push函数,能将其参数加入到一个数组中。

重载

 对于文章中的重载不是很理解,因为JavaScript是没有重载这一概念的,
1
2
3
4
5
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string | void {
if (typeof x === 'number') {
return Number(x.to...

Playground Link