了不起的 TypeScript 入门教程「基础篇」

admin 2025-01-04 169人围观 ,发现222个评论


作者:semlinker全栈修仙之路

前言

目录

了不起的TypeScript入门教程「基础篇」

了不起的TypeScript入门教程「实践篇」

想学习TypeScript的小伙伴看过来,本文将带你一步步学习TypeScript入门相关的十四个知识点,详细的内容大纲请看下图:


一、TypeScript是什么

TypeScript是一种由微软开发的自由和开源的编程语言。它是JavaScript的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。

TypeScript提供最新的和不断发展的JavaScript特性,包括那些来自2015年的ECMAScript和未来的提案中的特性,比如异步功能和Decorators,以帮助建立健壮的组件。下图显示了TypeScript与ES5、ES2015和ES2016之间的关系:

1.1TypeScript与JavaScript的区别

TypeScriptJavaScriptJavaScript的超集用于解决大型项目的代码复杂性一种脚本语言,用于创建动态网页。可以在编译期间发现并纠正错误作为一种解释型语言,只能在运行时发现错误强类型,支持静态和动态类型弱类型,没有静态类型选项最终被编译成JavaScript代码,使浏览器可以理解可以直接在浏览器中使用支持模块、泛型和接口不支持模块,泛型或接口支持ES3,ES4,ES5和ES6等不支持编译其他ES3,ES4,ES5或ES6功能社区的支持仍在增长,而且还不是很大大量的社区支持以及大量文档和解决问题的支持

1.2获取TypeScript

命令行的TypeScript编译器可以使用包来安装。

1.安装TypeScript

nbsp;npminstall-gtypescript

2.编译TypeScript文件

nbsp;#=

当然,对于刚入门TypeScript的小伙伴,也可以不用安装typescript,而是直接使用线上的TypeScriptPlayground来学习新的语法或新特性。

二、TypeScript基础类型2.1Boolean类型
letisDone:boolean=false;//ES5:varisDone=false;
2.2Number类型
letcount:number=10;//ES5:varcount=10;
String类型
letname:string="Semliker";//ES5:varname='Semlinker';
2.4Array类型
letlist:number[]=[1,2,3];//ES5:varlist=[1,2,3];letlist:Arraynumber=[1,2,3];//Arraynumber泛型语法//ES5:varlist=[1,2,3];
2.5Enum类型

使用枚举我们可以定义一些带名字的常量。使用枚举可以清晰地表达意图或创建一组有区别的用例。TypeScript支持数字的和基于字符串的枚举。

1.数字枚举

enumDirection{NORTH,SOUTH,EAST,WEST,}letdir:Direction=;

默认情况下,NORTH的初始值为0,其余的成员会从1开始自动增长。换句话说,的值为1,的值为2,的值为3。上面的枚举示例代码经过编译后会生成以下代码:

"usestrict";varDirection;(function(Direction){Direction[(Direction["NORTH"]=0)]="NORTH";Direction[(Direction["SOUTH"]=1)]="SOUTH";Direction[(Direction["EAST"]=2)]="EAST";Direction[(Direction["WEST"]=3)]="WEST";})(Direction||(Direction={}));vardir=;

当然我们也可以设置NORTH的初始值,比如:

enumDirection{NORTH=3,SOUTH,EAST,WEST,}

2.字符串枚举

在版本,允许我们使用字符串枚举。在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。

enumDirection{NORTH="NORTH",SOUTH="SOUTH",EAST="EAST",WEST="WEST",}

3.异构枚举

异构枚举的成员值是数字和字符串的混合:

enumEnum{A,B,C="C",D="D",E=8,F,}
2.6Any类型

在TypeScript中,任何类型都可以被归为any类型。这让any类型成为了类型系统的顶级类型(也被称作全局超级类型)。

letnotSure:any=666;notSure="Semlinker";notSure=false;

any类型本质上是类型系统的一个逃逸舱。作为开发者,这给了我们很大的自由:TypeScript允许我们对any类型的值执行任何操作,而无需事先执行任何形式的检查。比如:

letvalue:any;;//();//OKvalue();//OKnewvalue();//OKvalue[0][1];//OK

在许多场景下,这太宽松了。使用any类型,可以很容易地编写类型正确但在运行时有问题的代码。如果我们使用any类型,就无法使用TypeScript提供的大量的保护机制。为了解决any带来的问题,引入了unknown类型。

2.7Unknown类型

就像所有类型都可以赋值给any,所有类型也都可以赋值给unknown。这使得unknown成为TypeScript类型系统的另一种顶级类型(另一种是any)。下面我们来看一下unknown类型的使用示例:

letvalue:unknown;value=true;//OKvalue=42;//OKvalue="HelloWorld";//OKvalue=[];//OKvalue={};//OKvalue=;//OKvalue=null;//OKvalue=undefined;//OKvalue=newTypeError();//OKvalue=Symbol("type");//OK

对value变量的所有赋值都被认为是类型正确的。但是,当我们尝试将类型为unknown的值赋值给其他类型的变量时会发生什么?

letvalue:unknown;letvalue1:unknown=value;//OKletvalue2:any=value;//OKletvalue3:boolean=value;//Errorletvalue4:number=value;//Errorletvalue5:string=value;//Errorletvalue6:object=value;//Errorletvalue7:any[]=value;//Errorletvalue8:Function=value;//Error

unknown类型只能被赋值给any类型和unknown类型本身。直观地说,这是有道理的:只有能够保存任意类型值的容器才能保存unknown类型的值。毕竟我们不知道变量value中存储了什么类型的值。

现在让我们看看当我们尝试对类型为unknown的值执行操作时会发生什么。以下是我们在之前any章节看过的相同操作:

letvalue:unknown;;//();//Errorvalue();//Errornewvalue();//Errorvalue[0][1];//Error

将value变量类型设置为unknown后,这些操作都不再被认为是类型正确的。通过将any类型改变为unknown类型,我们已将允许所有更改的默认设置,更改为禁止任何更改。

2.8Tuple类型

众所周知,数组一般由同种类型的值组成,但有时我们需要在单个变量中存储不同类型的值,这时候我们就可以使用元组。在JavaScript中是没有元组的,元组是TypeScript中特有的类型,其工作方式类似于数组。

元组可用于定义具有有限数量的未命名属性的类型。每个属性都有一个关联的类型。使用元组时,必须提供每个属性的值。为了更直观地理解元组的概念,我们来看一个具体的例子:

lettupleType:[string,boolean];tupleType=["Semlinker",true];

在上面代码中,我们定义了一个名为tupleType的变量,它的类型是一个类型数组[string,boolean],然后我们按照正确的类型依次初始化tupleType变量。与数组一样,我们可以通过下标来访问元组中的元素:

(tupleType[0]);//(tupleType[1]);//true

在元组初始化的时候,如果出现类型不匹配的话,比如:

tupleType=[true,"Semlinker"];

此时,TypeScript编译器会提示以下错误信息:

[0]:Type'true'isnotassignabletotype'string'.[1]:Type'string'isnotassignabletotype'boolean'.

很明显是因为类型不匹配导致的。在元组初始化的时候,我们还必须提供每个属性的值,不然也会出现错误,比如:

tupleType=["Semlinker"];

此时,TypeScript编译器会提示以下错误信息:

Property'1'ismissingintype'[string]'butrequiredintype'[string,boolean]'.
2.9Void类型

某种程度上来说,void类型像是与any类型相反,它表示没有任何类型。当一个函数没有返回值时,你通常会见到其返回值类型是void:

//声明函数返回值为voidfunctionwarnUser():void{("Thisismywarningmessage");}

以上代码编译生成的ES5代码如下:

"usestrict";functionwarnUser(){("Thisismywarningmessage");}

需要注意的是,声明一个void类型的变量没有什么作用,因为它的值只能为undefined或null:

letunusable:void=undefined;
2.10Null和Undefined类型

TypeScript里,undefined和null两者有各自的类型分别为undefined和null。

letu:undefined=undefined;letn:null=null;

默认情况下null和undefined是所有类型的子类型。就是说你可以把null和undefined赋值给number类型的变量。然而,如果你指定了--strictNullChecks标记,null和undefined只能赋值给void和它们各自的类型。

2.11Never类型

never类型表示的是那些永不存在的值的类型。例如,never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。

//返回never的函数必须存在无法达到的终点functionerror(message:string):never{thrownewError(message);}functioninfiniteLoop():never{while(true){}}

在TypeScript中,可以利用never类型的特性来实现全面性检查,具体示例如下:

typeFoo=string|number;functioncontrolFlowAnalysisWithNever(foo:Foo){if(typeoffoo==="string"){//这里foo被收窄为string类型}elseif(typeoffoo==="number"){//这里foo被收窄为number类型}else{//foo在这里是neverconstcheck:never=foo;}}
三、TypeScript断言

有时候你会遇到这样的情况,你会比TypeScript更了解某个值的详细信息。通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。

通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。类型断言好比其他语言里的类型转换,但是不进行特殊的数据检查和结构。它没有运行时的影响,只是在编译阶段起作用。

类型断言有两种形式:

3.1“尖括号”语法
letsomeValue:any="thisisastring";letstrLength:number=(stringsomeValue).length;
3.2as语法
letsomeValue:any="thisisastring";letstrLength:number=(someValueasstring).length;
四、类型守卫

类型保护是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内。换句话说,类型保护可以保证一个字符串是一个字符串,尽管它的值也可以是一个数值。类型保护与特性检测并不是完全不同,其主要思想是尝试检测属性、方法或原型,以确定如何处理值。目前主要有四种的方式来实现类型保护:

4.1in关键字
interfaceAdmin{name:string;privileges:string[];}interfaceEmployee{name:string;startDate:Date;}typeUnknownEmployee=Employee|Admin;functionprintEmployeeInformation(emp:UnknownEmployee){("Name:"+);if("privileges"inemp){("Privileges:"+);}if("startDate"inemp){("StartDate:"+);}}
4.2typeof关键字
functionpadLeft(value:string,padding:string|number){if(typeofpadding==="number"){returnArray(padding+1).join("")+value;}if(typeofpadding==="string"){returnpadding+value;}thrownewError(`Expectedstringornumber,got'${padding}'.`);}

typeof类型保护只支持两种形式:typeofv==="typename"和typeofv!==typename,"typename"必须是"number","string","boolean"或"symbol"。但是TypeScript并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。

4.3instanceof关键字
interfacePadder{getPaddingString():string;}classSpaceRepeatingPadderimplementsPadder{constructor(privatenumSpaces:number){}getPaddingString(){returnArray(+1).join("");}}classStringPadderimplementsPadder{constructor(privatevalue:string){}getPaddingString(){;}}letpadder:Padder=newSpaceRepeatingPadder(6);if(padderinstanceofSpaceRepeatingPadder){//padder的类型收窄为'SpaceRepeatingPadder'}
4.4自定义类型保护的类型谓词
functionisNumber(x:any):xisnumber{returntypeofx==="number";}functionisString(x:any):xisstring{returntypeofx==="string";}
五、联合类型和类型别名5.1联合类型

联合类型通常与null或undefined一起使用:

constsayHello=(name:string|undefined)={/**/};

例如,这里name的类型是string|undefined意味着可以将string或undefined的值传递给sayHello函数。

sayHello("Semlinker");sayHello(undefined);

通过这个示例,你可以凭直觉知道类型A和类型B联合后的类型是同时接受A和B值的类型。

5.2可辨识联合

TypeScript可辨识联合(DiscriminatedUnions)类型,也称为代数数据类型或标签联合类型。它包含3个要点:可辨识、联合类型和类型守卫。

这种类型的本质是结合联合类型和字面量类型的一种类型保护方法。如果一个类型是多个类型的联合类型,且多个类型含有一个公共属性,那么就可以利用这个公共属性,来创建不同的类型保护区块。

1.可辨识

可辨识要求联合类型中的每个元素都含有一个单例类型属性,比如:

enumCarTransmission{Automatic=200,Manual=300}interfaceMotorcycle{vType:"motorcycle";//discriminantmake:number;//year}interfaceCar{vType:"car";//discriminanttransmission:CarTransmission}interfaceTruck{vType:"truck";//discriminantcapacity:number;//intons}

在上述代码中,我们分别定义了Motorcycle、Car和Truck三个接口,在这些接口中都包含一个vType属性,该属性被称为可辨识的属性,而其它的属性只跟特性的接口相关。

2.联合类型

基于前面定义了三个接口,我们可以创建一个Vehicle联合类型:

typeVehicle=Motorcycle|Car|Truck;

现在我们就可以开始使用Vehicle联合类型,对于Vehicle类型的变量,它可以表示不同类型的车辆。

3.类型守卫

下面我们来定义一个evaluatePrice方法,该方法用于根据车辆的类型、容量和评估因子来计算价格,具体实现如下:

constEVALUATION_FACTOR=;functionevaluatePrice(vehicle:Vehicle){*EVALUATION_FACTOR;}constmyTruck:Truck={vType:"truck",capacity:9.5};evaluatePrice(myTruck);

对于以上代码,TypeScript编译器将会提示以下错误信息:

Property'capacity'doesnotexistontype'Vehicle'.Property'capacity'doesnotexistontype'Motorcycle'.

原因是在Motorcycle接口中,并不存在capacity属性,而对于Car接口来说,它也不存在capacity属性。那么,现在我们应该如何解决以上问题呢?这时,我们可以使用类型守卫。下面我们来重构一下前面定义的evaluatePrice方法,重构后的代码如下:

functionevaluatePrice(vehicle:Vehicle){switch(){case"car":*EVALUATION_FACTOR;case"truck":*EVALUATION_FACTOR;case"motorcycle":*EVALUATION_FACTOR;}}

在以上代码中,我们使用switch和case运算符来实现类型守卫,从而确保在evaluatePrice方法中,我们可以安全地访问vehicle对象中的所包含的属性,来正确的计算该车辆类型所对应的价格。

5.3类型别名

类型别名用来给一个类型起个新名字。

typeMessage=string|string[];letgreet=(message:Message)={//};
六、交叉类型

TypeScript交叉类型是将多个类型合并为一个类型。这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。

interfaceIPerson{id:string;age:number;}interfaceIWorker{companyId:string;}typeIStaff=IPersonIWorker;conststaff:IStaff={id:'E1006',age:33,companyId:'EFT'};(staff)

在上面示例中,我们首先为IPerson和IWorker类型定义了不同的成员,然后通过运算符定义了IStaff交叉类型,所以该类型同时拥有IPerson和IWorker这两种类型的成员。

七、TypeScript函数7.1TypeScript函数与JavaScript函数的区别

TypeScriptJavaScript含有类型无类型箭头函数箭头函数(ES2015)函数类型无函数类型必填和可选参数所有参数都是可选的默认参数默认参数剩余参数剩余参数函数重载无函数重载

7.2箭头函数

1.常见语法

(()=('reading'));(title=(title));((title,idx,arr)=(idx+'-'+title););((title,idx,arr)={(idx+'-'+title);});

2.使用示例

//未使用箭头函数functionBook(){letself=this;=2016;setInterval(function(){();},1000);}//使用箭头函数functionBook(){=2016;setInterval(()={();},1000);}
7.3参数类型和返回类型
functioncreateUserId(name:string,id:number):string{returnname+id;}
7.4函数类型
letIdGenerator:(chars:string,nums:number)=string;functioncreateUserId(name:string,id:number):string{returnname+id;}IdGenerator=createUserId;
7.5可选参数及默认参数
//可选参数functioncreateUserId(name:string,age?:number,id:number):string{returnname+id;}//默认参数functioncreateUserId(name:string="Semlinker",age?:number,id:number):string{returnname+id;}
7.6剩余参数
functionpush(array,items){(function(item){(item);});}leta=[];push(a,1,2,3);
7.7函数重载

函数重载或方法重载是使用相同名称和不同参数数量或类型创建多个方法的一种能力。要解决前面遇到的问题,方法就是为同一个函数提供多个函数类型定义来进行函数重载,编译器会根据这个列表去处理函数的调用。

functionadd(a:number,b:number):number;functionadd(a:string,b:string):string;functionadd(a:string,b:number):string;functionadd(a:number,b:string):string;functionadd(a:Combinable,b:Combinable){if(typeofa==="string"||typeofb==="string"){()+();}returna+b;}

在以上代码中,我们为add函数提供了多个函数类型定义,从而实现函数的重载。之后,可恶的错误消息又消失了,因为这时result变量的类型是string类型。在TypeScript中除了可以重载普通函数之外,我们还可以重载类中的成员方法。

方法重载是指在同一个类中方法同名,参数不同(参数类型不同、参数个数不同或参数个数相同时参数的先后顺序不同),调用时根据实参的形式,选择与它匹配的方法执行操作的一种技术。所以类中成员方法满足重载的条件是:在同一个类中,方法名相同且参数列表不同。下面我们来举一个成员方法重载的例子:

classCalculator{add(a:number,b:number):number;add(a:string,b:string):string;add(a:string,b:number):string;add(a:number,b:string):string;add(a:Combinable,b:Combinable){if(typeofa==="string"||typeofb==="string"){()+();}returna+b;}}constcalculator=newCalculator();constresult=("Semlinker","Kakuqo");

这里需要注意的是,当TypeScript编译器处理函数重载时,它会查找重载列表,尝试使用第一个重载定义。如果匹配的话就使用这个。因此,在定义重载的时候,一定要把最精确的定义放在最前面。另外在Calculator类中,add(a:Combinable,b:Combinable){}并不是重载列表的一部分,因此对于add成员方法来说,我们只定义了四个重载方法。

本篇文章未完结,请看下一篇

推荐TypeScript知识点文章

了不起的TypeScript入门教程「基础篇」

了不起的TypeScript入门教程「实践篇」

TypeScript常见问题整理(60多个)「上」

TypeScript常见问题整理(60多个)「下」

深入TypeScript难点梳理讲解

之前你必须知道的TypeScript实战技巧

深入TypeScript难点梳理讲解

之前你必须知道的TypeScript实战技巧

你需要的React+TypeScript50条规范和经验

TypeScript详细概括【思维导图】

尝鲜HookTypeScript取代Vuex【项目实践】

TypeScript详细概括【思维导图】

「新消息」基于JavaScript/TypeScript编程环境即将发布

「干货」一张页面引起的项目架构思考(rax+Typescript+hooks)

深入浅出Vue3跟着尤雨溪学TypeScript之Ref【实践】

作者:semlinker全栈修仙之路

猜你喜欢
    不容错过