前言
现在TypeScript越来越火,咱也赶一下潮流,开始学习一下TypeScript,在学习的同时做笔记记录,希望可以共同学习,在未来的程序生涯中可以使用TypeScript来进行开发。
TypeScript是什么?
TypeScript 是 JavaScript 的一个超集,主要提供了类型系统和对 ES6 的支持,因为JavaScript是弱类型语言,我们在开发的时候,很容易会因为数据类型的原因造成一些不明不白的错误,所以,在TypeScript中,对类型的检测是极为严格的。
TypeScript的优势
在这里我借用阮一峰大神的文章来说一下TypeScript的优势。
-
TypeScript 增加了代码的可读性和可维护性;
- 类型系统实际上是最好的文档,大部分的函数看看类型的定义就可以知道如何使用了
- 可以在编译阶段就发现大部分错误,这总比在运行时候出错好
- 增强了编辑器和 IDE 的功能,包括代码补全、接口提示、跳转到定义、重构等
-
TypeScript 非常包容;
- TypeScript 是 JavaScript 的超集,
.js
文件可以直接重命名为.ts
即可 - 即使不显式的定义类型,也能够自动做出类型推论
- 可以定义从简单到复杂的几乎一切类型
- 即使 TypeScript 编译报错,也可以生成 JavaScript 文件
- 兼容第三方库,即使第三方库不是用 TypeScript 写的,也可以编写单独的类型文件供 TypeScript 读取
- TypeScript 是 JavaScript 的超集,
-
TypeScript 拥有活跃的社区;
- 大部分第三方库都有提供给 TypeScript 的类型定义文件
- Google 开发的 Angular2 就是使用 TypeScript 编写的
- TypeScript 拥抱了 ES6 规范,也支持部分 ESNext 草案的规范
TypeScript的缺点
- 无非就是难学了一点,无所谓啦,反正再难学还是要学的!自己选的路,跪着也要走下去。
写在前边
首先一定要从JavaScript的弱类型思想中走出来,在TypeScript中,保证在大部分时候都要给数据定义类型。
其次一定要坚持,坚持,坚持。
那就先从基础开始吧!
安装使用
- 首先全局安装typescript
npm install typescript -g
- 当全局安装后,我们就可以在全局下使用
tsc
指令了
先通过tsc -v
查看一下当前的TypeScript的版本号,顺便看一下有没有安装成功。tsc -v
- 接着新建一个文件夹,新建一个
.ts
文件,例如:index.ts
;
在使用TypeScript时,我们使用.ts
为后缀的文件,如果是使用React的时候,我们默认使用.tsx
为后缀的文件。它们分别对应的是.js
和.jsx
文件。 - 在
index.ts
文件中,写入如下代码function sayHello(person: string) { return 'Hello' + person; } let person = [1, 2, 3]; sayHello(person);
- 打开命令提示符,通过如下指令编译TypeScript文件
tsc index.ts
- 执行会发现命令提示符报错
index.ts:5:10 - error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'string'
,因为我们给函数的参数定义成为string类型,而我们传入的是一个数组,所以会报错。 - 但是虽然报错了,依旧生成了
index.js
文件,这是因为TypeScript文件就算编译出错也依旧会编译成js文件,毕竟这点错误在咱们的JavaScript里边不算什么。 - 接着修改我们的
index.ts
文件function sayHello(person: string) { return 'Hello' + person; } let person: string = '郝晨光'; sayHello(person);
- 再次执行
tsc index.ts
指令 - 这次命令提示符没有任何的错误信息,并且可以看到生成的
index.js
文件。
基本类型
对于基本类型,直接定义类型即可。定义好就必须指定类型,不可以变成其它类型。
- 定义布尔类型的值
// 布尔值 let isDone: boolean = true;
- 定义数字类型的值
// 数字 let count: number = 1;
- 定义字符串类型的值
// 字符串 let str:string = 'typescript';
- 定义空值
空值是typescript中新引入的一种基本类型,可以用 void 表示没有任何返回值的函数。
或者声明一个空值,但是这样毫无意义,因为只能被赋值为undefined和null。function alertName(): void { alert('郝晨光加油!') }
let myVoid1: void = undefined; let myVoid2: void = null;
- 定义null和undefined
需要注意的是,null和undefined可以被赋予任何类型,但是void的空值不能被赋予其他类型。// null let nullSet: null = null; // undefined let undeSet: undefined = undefined;
let unsetNumber: number = null; let unsetString: string = undefined;
- 任意类型
我们都非常熟悉JavaScript,在JavaScript中,我们的变量是可以随时转换数据类型的,但是在TypeScript中,对于定义好的基本数据类型是不可以的!那我们如何实现一个不一定是什么数据类型的值呢?就该使用任意类型了。// 任意类型 // 对于任意类型,可以在赋值后修改数据类型。 let myAny: any = 'string'; myAny = 123; // 变量在声明的时候没有指定类型,并且没有赋值,自动编译为 any 类型 let anyThing; anyThing = '未指定类型并且未赋值,自动编译为 any 类型'; anyThing = 1234;
类型推断
了解了基本数据类型,接下来就应该先了解一下TypeScript的类型推断,在TypeScript中,如果你没有给(变量/数据)指定一个数据类型,分为两种情况,
- 一种是定义未赋值,就是我们在上边的任意类型中提到的,它会自动声明成为any类型。
- 而如果我们在定义(变量/数据)的时候,给它赋值,但是不给它指定类型的话,TypeScript就会进行类型推断。
在上边的案例中,在定义stringThing变量定义时,给它赋值成为string,经过TypeScript的类型推断后,它会被设置成为string数据类型,而当我们给它赋值成为number类型时,TypeScript就会进行报错。// 但是如果变量在声明是赋值了,但是没有指定类型 // 经过类型推断,它应该是赋值时候的数据类型,即为string。 let stringThing = '未指定类型,但是赋值为string'; stringThing = 1234; // 报错
联合类型
我们在实际开发中经常会遇到这样的问题,一个参数,它既有可能是string类型,又有可能是number类型,那这种情况,如果我们用TypeScript应该怎么定义呢?肯定不可能使用any吧,那样的话,我们还可以设置其他类型的值。所以,就出现了联合类型。
// 联合类型(多个类型)
let numString: string | number = '一开始是string';
numString = 123456789; // 可以被赋值为number类型
numString = true; // 报错,不可以是boolean类型。
但是,针对联合类型,只能使用它们之间公共的方法,不能使用其中一个特有的方法。例如:
// 由于number是没有length属性的,所以会报错
function getLength(some: string | number): number {
return some.length;
}
访问number和string的公共属性没有任何问题,不会报错。
function getString(some: string | number): string {
return some.toString();
}
联合类型会在被赋值的时候推断出一个类型,并监听除当前数据类型以外的错误。
let myName: string | number;
myName = '郝晨光';
// 此时是字符串类型,所以调用length属性没有任何问题
console.log(myName.length); // 3
myName = 123;
// 此时是number类型,没有lenght属性,所以会报错
console.log(myName); // error TS2339: Property 'length' does not exist on type 'number'.
对象的类型
在ts中,通过接口(interface)来定义对象的类型;
-
接口定义了对象的值的类型,并且定义了对象的参数,多一个参数少一个参数都不可以。
interface Person { name: string, age: number, sex: string } let person: Person = { name: '郝晨光', age: 24, sex: '男' };
-
当我们的对象有一些属性不是必须的时候,可以通过?来定义,?表示属性是可选的。
interface People { name: string, age: number, sex?: string } let girl: People = { name: 'girl', age: 20 };
-
当我们的对象有一些未知的属性时,可以通过[propName]来定义
interface Student { name: string, age?: number, [propName: string]: string | number } let student: Student = { name: '学生', age: 20, className: 'A班' };
- 如果设置了propName的话,可选的值(带?的值),就必须是propName的指定值的子类型
- 例如any,所有类型都是它的子类型,或者通过联合类型来指定
- 在上边的案例中,如果我只给propName指定数据类型为string的话,TypeScript就会报错,因为age属性需要的是number类型的值。
-
我们还可以给对象设置只读的属性。
// 只读属性 // 通过 readonly 属性来设置属性是只读的 interface Teacher { readonly id: number, name: string, age?: number, [propName: string]: any } let teacher: Teacher = { id: 123456, name: '郝晨光', age: 24, className: 'A班' }; console.log(teacher.id); teacher.id = 1231546; // 报错,因为是只读的,不能修改
数组的类型
在TypeScript中定义数组的方式有很多
- 类型 + 方括号
let arr1: number[] = [1, 2, 3]; // 如果出现非number的值,就会报错 arr1 = [1, 2, '1', 3]; // 报错 arr1.push(10); // 正常 arr1.push('10'); // 报错
- 数组泛型
let arr2: Array<number> = [1, 2, 3]; arr2 = [1, 2, '1', 3]; // 报错; arr2.push(10); //正常 arr2.push('10'); // 报错
- 使用接口interface定义
interface NumberArray { [index: number]: number // 定义数据是number类型 push(number: number): void; // 定义push方法接收number类型的值,并返回空值 } let arr3: NumberArray = [1, 2, 3]; arr3 = [1, 2, '1', 3]; // 报错; arr3.push(10); // 正常 arr3.push('10'); // 报错
- 使用any定义数组
// 数组中允许出现任意类型的值 let arr4: any[] = [1, '2', new Date(), 3];
- 类数组
类数组不能通过数组的方式定义,每一个类数组都有它对应的内置对象arguments ==> IArguments NodeList ==> NodeList HTMLCollection ==> HTMLCollection ···
函数的类型
在JavaScript中,定义函数有两种方式,一种是函数声明式,另一种是函数表达式。
在函数定义之后,输入多余的(或者少于要求的)参数,是不被允许的。
- 函数声明式
function fn(x: number, y: number): number { return x + y; }
- 函数表达式
注意:这样声明对于左边的变量来说,并没有任何数据指定,虽然不会报错,但是是不严谨的
所以我们应该这样定义。let fn2 = function(x: number, y: number): number { return x + y; };
需要注意的是TypeScript中,用在此处的 => 与 ES6中的箭头函数是没有任何关系的,此处只是为了指定返回值的数据类型。let fn3: (x: number, y: number) => number = function(x: number, y: number): number { return x + y; };
- 使用接口interface定义函数
interface Fun { (x: number, y: number): number } // 此时可以不指定右边的函数的返回值的类型,因为在接口中定义了 let fn4: Fun = function(x: number, y: number) { return x + y; };
- 可选参数
需要注意的是,可选参数必须出现在最后,不能在可选参数之后又出现必须参数。function fn5(firstName: string, lastName?: string): string { return firstName + ' ' + lastName; }
下面的案例会报错!
函数出现多个可选参数function fn6(x?: number,y: number): number { return x + y; }
// 出现多个可选参数 function fn7(x: number,y?: number, z?: number): number { return x + y + z; }
- 参数默认值
当定义了默认值时,可选参数将不会在受限制,可以出现在必须参数前边,调用时直接传入null或者undefined即可。function fn8(x: number = 5,y: number):number { return x + y; } fn8(null,10); // 15 fn8(10, 20); // 30
- 剩余参数
在ES6中,可以通过扩展运算符来接收剩余的参数,剩余参数其实就是一个数组,所以可以通过any[]或者Array<any>等等方式定义类型。function fn9(arr: any[], ...args: any[]): Array<any> { args.forEach(item => { arr.push(item); }); return arr; }
- 函数重载
重载就是指函数在接收不同类型的参数或者不同数量的参数时,做出不同的处理,返回不同的结果。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(x === 'string'){ return x.split('').reverse().join(''); } } reverse(123456); reverse('123456');
本文参考:TypeScript入门教程 - 阮一峰
如果本文对您有帮助,可以看看本人的其他文章:
简单实现Vue响应式原理@郝晨光
前端常见面试题(十六)@郝晨光
前端常见面试题(十五)@郝晨光