为什么使用TS和TS运行环境搭建
TypeScript是微软开发的一个开源的编程语言,通过在JavaScript的基础上添加静态类型定义构建而成。TypeScript通过TypeScript编译器或Babel转译为JavaScript代码,可运行在任何浏览器,任何操作系统。
TS文件需要编译成JS文件
首先我们的浏览器是不认识TS文件的,所以需要把TS编译成JS文件才可以,TS官网提供了一种方式,就是去全局安装typescript这个模块,命令如下:
npm install -g typescript
安装好后,就可以使用 tsc xxx.ts
命名把TS文件自动转化成对应的JS文件了,在同级目录下就会生成一个xxx.js的文件,这个文件就是编译后的文件,浏览器是可以认识的。
在TS编译JS文件的时候,会有一些细节,我们一起来看一下:
第一,在tsc
命令进行转换操作的时候,是不能实时进行转换的,那么可以通过添加一个-w
的参数来完成实时转换的操作
tsc xxx.ts -w
第二,在编译后,我们会发现TS文件中定义的变量会产生错误的波浪线,这主要是因为TS默认是全局环境下的,所以跟其他文件中的同名变量冲突了,那么该如何解决呢?可以把全局环境改成局部的环境,只需要把TS文件变成一个模块化的文件,那么变量就只在模块内起作用,这样就不会产生冲突,从而引发错误的波浪线。
let foo = 123;
export {};
第三,如果不想采用默认的编译方式,那么可以通过修改配置文件来改变一些默认的行为。配置文件需要叫做,tsconfig.json
。
可以通过tsc --init
命令自动化的创建tsconfig.json
这个文件,这个文件中的配置选项非常的多,我们会在后面小节中给大家进行详解的讲解,这里先修改其中的一两个配置,来感受一下配置文件的一个作用。
{
“compilerOptions”: {
"outDir": "./dist", //编译后文件输出的位置
"module": "ES6", //转换后模块的风格
"target": "ES5" //转换成JS不同语法版本
},
"include": ["xxx.ts"] //只对那些文件进行编译
}
通过命令行输入tsc
命令就可以自动去调用tsconfig.json
这个文件了,非常的方便。
为什么要使用TS去写程序
TS编写代码的好处诸多,下面我们列出几点:
- 友好的IDE提示
- 强制类型,防止报错
- 语言编写更加严谨
- 快速查找到拼写错误
- JS的超集,扩展新功能
下面通过几个小例子来感受一下TS比JS要优秀的点,
let a = 123;
a.map(()=>{}) // error
这段代码在TS中是会报错的,因为a是一个数字,不会有map方法,而JS是不会给我们进行提示的。
let a = 1;
if(a === 1 && a === 2) { // error
}
这段代码在TS中是会报错的,因为从逻辑上来看这段代码并没有意义,因为a同一时间不可能既是1又是2。
let a = {
username: 'xiaoming',
age: 20
}
这段代码TS提示会非常的好,不会夹杂一些其他的提示信息,而且一旦单词写错了,TS会有非常好的提示效果。
类型声明空间与变量声明空间
变量声明空间
在JS中我们只有变量声明空间,比如let a = 123
。但是在TS中我们不仅存在变量声明空间,而且还存在类型声明空间。下面就让我们一起去探索一下类型声明空间的特点吧。
类型声明空间
可以通过type
关键字来定义类型声明空间,type
在TS中叫做类型别名。为了能够更好的区分两个空间,所以人为规定类型空间定义的名字首字母要大写,例如:type A = number
。
这里要注意,不要把两个空间进行互相赋值,会产生错误的,如下:
let a = 'hello';
type B = a; // error
type A = string;
let a = A; // error
但是在TS中,有的语法它既是变量声明空间也是类型声明空间,比如:类语法。
class Foo {} // 类在TS中既是变量声明空间,也是类型声明空间
let a = Foo; // success
type A = Foo; // success
那么两个空间之间的关系有是什么呢?下一小节来进行学习。
类型注解与类型推断
上一个小节中,给大家留了一个思考题,那就是如何把两个空间联系到一起?主要采用类型注解来实现的。
类型注解
通过把变量空间与类型空间结合在一起的操作,就叫做类型注解,具体语法是通过冒号连接在一起的。
let a: string = 'hello'
//也可以通过类型别名进行连接
type A = string
let a: A = 'hello'
这样定义的a变量就具备了字符串类型,那么我们尝试给a修改成其他类型就会报错,现在就强制a的类型为字符串了,这也是TS强制类型的特点。
那么我们不去进行类型注解,是不是就不会强制类型了呢?其实也会强制类型的,因为TS可以进行自动类型推断的。
类型推断
TS中会对不进行类型注解的变量,进行自动类型推断的,如下:
// let a: string -> 类型推断
let a = 'hello'
a = 123; // error
所以在TS中不管是类型注解还是自动类型推断,对于类型都不能轻易的做修改,这样可以保证变量的类型稳定,从而减少BUG的产生。
类型分类与联合类型与交叉类型
类型分类
在TS中会把类型大致划分为三部分:
- 基本类型:string number boolean null undefined symbol bigint
- 对象类型:[] {} function(){}
- TS新增类型:any never void unknown enum
对于基本类型,我们就稍微演示一下,毕竟都是JS已有的内容:
let a: string = 'hello'
let b: bigint = 1n;
对象类型也稍微演示一下,更具体的对象类型会在后面的小节中进行讲解:
let c: object = [];
c = function(){}; // success
联合类型
在TS中如何能够让一个变量支持多种不同类型呢?那么就可以采用联合类型来实现。
let a: string|number|boolean = 'hello';
a = 123;
在类型中除了有或的操作外,就一定有与的操作,那么在TS中实现类型与的行为叫做,交叉类型。
交叉类型
类型之间进行与的操作,不过一般在对象中进行使用,基本类型很少采用交叉类型。
type A = {
username: string
}
type B = {
age: number
}
let a: A&B = { username: 'xiaoming', age: 20 }
这里a变量,必须具备A、B两个共同指定的类型才可以。
never类型与any类型与unknown类型
在本小节中将学习一些TS中提供的新的类型,比如:never类型,any类型,unknown类型。
never类型
never类型表示永不存在的值的类型,当一个值不存在的时候就会被自动类型推断成never类型。
// let a: never -> 不能将类型“number”分配给类型“never”
let a: number & string = 123
在这段代码中a变量要求类型既是数字又是字符串,而值是一个123无法满足类型的需求,所以a会被自动推断成never类型。所以never类型并不常用,只是在出现问题的时候会被自动转成never。
有时候也可以利用never类型的特点,实现一些小技巧应用,例如可以实现判断参数是否都已被使用,代码如下:
function foo(n: 1 | 2 | 3) {
switch (n) {
case 1:
break
case 2:
break
case 3:
break
default:
let m: never = n; // 检测n是否可以走到这里,看所有值是否全部被使用到
break
}
}
any类型和unknown类型
any类型表示任意类型,而unknown类型表示为未知类型,是any类型对应的安全类型。
既然any表示任意类型,那么定义的变量可以随意修改其类型,这样带来的问题就是TS不再进行类型强制,整个使用方式根JS没有任何区别。
let a: any = 'hello';
a = 123;
a = true;
a.map(()=>{}) // success
所以说any类型是TS中的后门,不到万不得已的时候尽量要少用,如果真的有这种需求的话,可以采用any对应的安全类型unknown来进行定义。
let a: unknown = 'hello';
a = 123;
// any不进行检测了,unknown使用的时候,TS默认会进行检测
a.map(()=>{}) // error
unknown类型让程序使用的时候更加严谨,我们必须主动告诉TS,这里是一个什么类型,防止我们产生误操作。那么怎样让unknown类型不产生错误呢?就需要配合类型断言去使用,下一个小节我们一起来学习吧。
类型断言与非空断言
类型断言
类型断言主要用于当 TypeScript 推断出来类型并不满足你的需求,你需要手动指定一个类型。在上一个小节中使用unknown类型的时候,需要手动指定当前是一个什么类型,来满足TS类型的需求检测,那么就可以采用类型断言来实现。
let a: unknown = 'hello';
a = 123;
(a as []).map(()=>{}) // success
这里就不会再报错了,当然类型断言只是告诉TS不要管我们了,这个只是在编译前的处理,就是欺骗TS。而在编译后,a确实不是一个数组,所以运行到浏览器后就会报错,那么我们在使用断言的时候还是要格外的小心,不要滥用断言操作。
非空断言
在类型断言中,也有一些特殊情况,如下:
let b: string|undefined = undefined;
b.length // error
因为b可能是字符串也可能是undefined,所以b.length
的时候就会报错,这样我们可以采用非空断言来告诉TS,这个b肯定不是undefined,所以b只能是字符串,那么b.length
就不会报错了。
let b: string|undefined = undefined;
b!.length // success
总结:类型断言是一种欺骗TS的手段,在编译阶段解决类型问题的,但是最终运行的结果需要开发人员自己负责,所以使用类型断言要严谨,否则最终还会产生报错。
数组类型与元组类型
在前面小节中介绍过对象类型,但是对象类型的范围太广了,本小节将具体讲解对象类型中的数组类型及元组类型。
数组类型
定义数组类型通常用两种写法:
- 类型[]
- Array<类型>
先看第一种方式,如下:
let arr1: (number|string)[] = [1, 2, 3, 'hello'];
再看第二种方式,如下:
let arr2: Array<number|string> = [1, 2, 3, 'hello'];
第二种写法是一种泛型的写法,这个会在后面章节中进行详解的。
这里数组子项可以是数字也可以是字符串。对于数组子项的顺序以及数组子项的个数并没有限制。那么如何能够对数组的个数以及顺序都做限定类型呢?就可以采用元组类型。
元组类型
元组类型实际上就是数组类型的一种特殊形式,代码如下:
let arr3: [number, string] = [1, 'hello'];
这里会限定数组的每一项的值的类型和值的个数,对于多添加一些子项都是会报错的,属于比较严格的数组形式。
对象类型与索引签名
对象类型
在TS中可以直接对对象字面量进行类型限定,可以精确到具体的字段都具备哪些类型,如下:
type A = {
username: string
age: number
}
let a: A = {
username: 'xiaoming',
age: 20
}
对于对象类型来说,多出来的字段还是缺少的字段都会产生错误,如下:
type A = {
username: string
age: number
}
let a: A = { // error
username: 'xiaoming'
}
那么可以给age添加一个可选标识符?
来表示age为可选项,写与不写都是可以的。
type A = {
username: string
//age是可选项
age?: number
}
let a: A = { // success
username: 'xiaoming'
}
索引签名
那么对于多出来的字段,可以通过索引签名方式来解决,如下:
type A = {
username: string
//索引签名
[index: string]: any
}
let a: A = {
username: 'xiaoming'
gender: 'male',
job: 'it'
}
索引签名中的属性也可以指定number
类型,不过往往只有数组中会采用这种数字类型的索引签名方式,如下:
type A = {
[index: number]: any
}
let a: A = [1, 2, 3, true, 'hello'];
对象类型如果想定义初始值为空值的话,可以采用类型断言来改造,如下:
type Obj = {username: string}
let obj = {} as Obj; // success
最后来看一下对象和数组组合在一起定义的类型,如下:
let json: {username: string, age: number}[] = [];
函数类型与void类型
函数类型
在TS中对于函数的类型使用方式有很多种限定,先来看一下函数的参数:
function foo(n: number, m?: string): number{
return 123;
}
foo(123, 'hello');
foo(123); // success
TS中要求,实参的个数跟形参的个数必须相同,所以可以添加可选链?
来实现参数可选操作。
函数还可以通过在函数中定义返回值的类型:number
,可以参考上面代码。
下面看一下函数表达式的写法,及类型注解的方式。
let foo: (n: number, m: string) => number = function(n, m){
return 123;
}
如果在前面进行了类型注解,那么就不用在函数体内进行类型的添加了。
void类型
表示函数没有任何返回值的时候得到的类型。
let foo = function(){ // void
}
当函数没有return的时候返回void类型,当return undefined的时候也可以返回void类型。那么函数定义void类型跟undefined类型也是有区别的,因为undefined 类型是不能不写return的。
let foo = function(): undefined{ // undefined 不能不写return的
} // error
函数重载与可调用注解
函数重载
函数重载是指函数约束传入不同的参数,返回不同类型的数据,而且可以清晰的知道传入不同的参数得到不同的结果。
假如我们实现这样一个函数,如下:
function foo(n1: number, n2?: number, n3?: number, n4?: number){
}
foo(1);
foo(1, 2);
foo(1, 2, 3);
foo(1, 2, 3, 4);
这样传递几个参数都是可以的,那么我们能不能限制传递几个参数呢?比如只允许传递一个参数,两个参数,四个参数,不允许传递三个参数,这时就可以通过函数重载还实现了。
function foo(n1: number): any
function foo(n1: number, n2: number): any
function foo(n1: number, n2: number, n3: number, n4: number): any
function foo(n1: number, n2?: number, n3?: number, n4?: number){
}
foo(1);
foo(1, 2);
foo(1, 2, 3); // error
foo(1, 2, 3, 4);
下面再来实现一个需求,要求参数的类型必须保持一致,如下:
function foo(n: number, m: number): any
function foo(n: string, m: string): any
function foo(n: number|string, m: number|string){
}
foo(1, 2);
foo('a', 'b');
foo(3, 'c'); // error
可调用注解
可调用注解提供多种调用签名,用以特殊的函数重载。首先可调用注解跟普通函数的类型注解可以起到同样的作用。
type A = () => void;
type A = { // 可调用注解,可以针对函数重载进行类型注解的
(): void
}
let a: A = () => {};
那么可调用注解比普通类型注解优势在哪里呢?就是可以对函数重载做类型注解,代码如下:
type A = {
(n: number, m: number): any
(n: string, m: string): any
}
function foo(n: number, m: number): any
function foo(n: string, m: string): any
function foo(n: number|string, m: number|string){
}
枚举类型与const枚举
枚举类型
枚举是组织收集有关联集合的一种方式,使代码更加易于阅读。其实简单来说枚举就是定义一组常量。
enum Roles {
SUPER_ADMIN,
ADMIN = 3,
USER
}
console.log( Roles.SUPER_ADMIN ); // 0
console.log( Roles.ADMIN ); // 3
console.log( Roles.USER ); // 4
枚举默认不给值的情况下,就是一个从0开始的数字,是可以自动进行累加的,当然也可以自己指定数值,后面的数值也是可以累加的。
枚举也支持反向枚举操作,通过数值来找到对应的key属性,这样操作起来会非常的灵活。
enum Roles {
SUPER_ADMIN,
ADMIN = 3,
USER
}
console.log( Roles[0] ); // SUPER_ADMIN
console.log( Roles[3] ); // ADMIN
console.log( Roles[4] ); // USER
枚举给我们的编程带来的好处就是更容易阅读代码,举例如下:
if(role === Roles.SUPER_ADMIN){ // 更容易阅读
}
下面来看一下,如果定义成字符串的话,需要注意一些什么?
enum Roles {
SUPER_ADMIN = 'super_admin',
ADMIN = 'admin',
USER = 'user'
}
字符串形式是没有默认值的,而且不能做反向映射的。
const枚举
在枚举的前面可以添加一个const关键字。
const enum Roles {
SUPER_ADMIN = 'super_admin',
ADMIN = 'admin',
USER = 'user'
}
那么没有const关键字和有const关键字的区别是什么呢?主要区别在于编译的最终结果,const方式最终编译出来的就是一个普通字符串,并不会产生一个对象,更有助于性能的体现。
总结内容
- 了解了什么是TypeScript,在编程中的优势
- 如何对TS文件进行编译,以及tsconfig.json的使用
- 全面掌握TS中的各种概念,如:类型注解、类型断言等
- 全面掌握TS中常见类型,如:数组、对象、函数等
- 全面掌握TS中的新类型,如:枚举、元组、any、never等
评论 (0)