深入浅出TypeScript 1.为什么要学TS
TypeScript
JavaScript
JavaScript的超集,用于解决大型项目的代码复杂性
一种脚本语言,用于创建动态网页
强类型,支持静态和动态类型
动态弱类型语言
可以在编译期间发现并纠正错误
只能在运行时发现错误
不允许改变变量的数据类型
变量可以被赋值成不同类型
特性
js的缺点:
数据类型
逻辑问题
访问不存在的属性
拼写错误不提示
编译Typescript 一.命令行编译 1.创建.ts文件
2.全局安装typescript
3.使用tsc命令编译
二.自动化编译 1.创建tsconfig.json文件
2.监视目录中.ts的文件变化
2. TS基础
基础类型
1 2 3 4 5 6 1. boolean ,number ,string 2. 枚举enum 3. any ,unknown ,void 4. never 5. 数组类型[]6. 元组类型tuple
2.类型声明
使用:来对变量或函数形参,进行类型声明:
1 2 3 4 5 let a : string funtion (x :number , y :number ):number { return x+y }let res = funtion (1 ,2 )
:也可以给变量附加上字面型变量,但不常见
3.类型推断
TS会根据我们的代码进行类型推导
类型推断不是万能的,面对复杂类型时推断容易出问题,尽量明确编写类型声明
4.interface 接口
1 2 3 4 5 6 定义:接口是为了定义对象类型 特点: -可选属性 -只读属性:readonly -可以描述函数类型 -可以描述自定义属性
5.类
1 2 3 4 5 6 7 8 定义:写法和JS 差不多,增加了一些定义 特点: -增加了public ,private ,protected 等修饰符 -抽象类; -只能被继承,不能被实例化 -作为基类,抽象方法必须被子类实现 -interface 约束类,使用implements关键字
3.TS进阶 1.高级类型
联合类型 |
1 2 3 let num : number | string num = 8 num = 'eight'
交叉类型 &
1 2 3 4 5 6 7 8 9 10 11 interface Person { name : string age : number }type Student = Person & { grade : number }const stu :Student stu.
类型断言
类型别名(type VS interface)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 -定义:给类型起个别名 -相同点: 1. 都可以定义函数或对象 2. 都允许继承 -差异点: 1. interface 是TS 用来定义对象,type 时用来定义别名方便使用; 2. type 可以定义基本类型,interface 不行; 3.interface 可以合并重复声明,type不行; ---相同点 interface Person1 { name : string age : number } type Person2 = { name : string age : number } const person1 :Person1 = { name :'lin, age: 18 } const person2:Person2 = { name:' lin', age: 18 } ---不同点 1. interface Person { name: string } interface Person { age: number } const person: Person = { name :' lin', age:18 } 2. type Person = {//Person报错 name:string } type Person = {//Person报错 age: number } const person : Person = { name: ' lin', age:18//整条报错 }
泛型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 基本定义:1. 泛型的语法是<>里面写类型参数2. 使用时有两种方法指定类型: 1. 定义要使用的类型 2. 通过TS 类型推断,自动推导类型 !3. 泛型的作用时临时占位,之后通过传来的类型进行推导;function print<T>(arg : T): T { console .log (arg); return arg; } print<string >('hello' ) print ('hello' )
基础操作符
typeof:获取类型
keyof:获取所有键
in:遍历枚举类型
T[K]:索引访问
extends:泛型约束
常用工具类型
Partial: 将类型属性变为可选
Required : 将类型属性变为必选
Readonly : 将类型属性变为只读
4.TS实战
声明文件
declear:三方库需要类型声明文件
.d.ts:声明文件定义
@types:三方库TS类型包
tsconfig.json:定义TS的配置
泛型约束后端接口类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import axios from 'axios' interface API { 'book/detail' : { id : number , }, 'book/comment' : { id : number comment : string } }function request<T extends keyof API >(url : T,obj : API [T]){ return axios.post (url, obj) }request ('book/comment' , { id : 1 , comment : 'good' })
5.ts常用类型 一.any any的含义是:任意类型。一旦将变量类型限制为any,意味着放弃了 对该变量的类型检查。
1 2 3 4 5 6 7 let a a = 99 a = false let b : string b = a console .log (b)
二.unknown unknown:未知类型
可以理解为一个类型安全的 any
1 2 3 4 5 6 let a : unknown a = 99 a = false let b : string b = a
会强制开发者在使用前进行类型检查,可以通过类型判断或类型断言的方式实现安全操作变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 let a : unknown a = 1 let x : string x = a if (typeof a === "string" ){ x = a } x = a as string x = <string >a
1 2 3 let str3 : unknown str3 = "hello" (str3 as string ).toUpperCase ()
3. never never:任何值都不是,不能有值。
几乎不使用never,对变量赋值无意义。
never一般是TypeScript主动推断出来的
1 2 3 4 5 6 7 8 let a : string a = "hello" if (typeof a === "string" ){ console .log (a) }else { console .log (a) }
never可用于限制函数的返回值(如抛出错误的情况),表示函数不能结束,或者不能正常结束。
1 2 3 function throwError (str :string ):never { throw new Error ('程序异常退出' +str) }
4. void void:用于函数返回值声明,表示函数返回值为空。
函数返回值为空,调用者也不应该依赖其返回值进行任何操作。
1 2 3 4 const logMsg = (msg :string ):void => { console .log (msg) }logMsg ('TS你好' )
注意:编码者没有编写return指定函数返回值,所以logMessage函数是没有显式返回值的,但会有一个隐式返回值 ,是undefined。虽然函数返回类型为void,但也是可以接受undefined的,简单记: undefined是void可以接受的一种“空”。
对函数返回值声明为void的函数,TS接受如下写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function logMessage (msg :string ):void {console .log (msg) }function logMessage (msg :string ):void {console .log (msg)return ; }function logMessage (msg :string ):void {console .log (msg)return undefined }
返回值类型为void的函数,调用者不应依赖其返回值进行任何操作! :理解来说,被void声明的函数返回值,TS希望开发者从语义层面上不要关注其返回值,不期望依赖该函数用于其他的操作。
1 2 3 4 5 6 7 8 9 10 11 12 function logMessage (msg :string ):void {console .log (msg) }let result = logMessage ('你好' )if (result){ console .function logMessage (msg :string ):undefined {console .log (msg) }let result = logMessage ('你好' )if (result){ console .log ('logMessage' )
理解 void 与 undefined: void是一个广泛的概念,用来表达“空”,而 undefined则是这种“空”的具体 实现。 因此可以说 undefined是void能接受的一种“空”的状态。 也可以理解为: void包含undefined,但void所表达的语义超越了undefi ned, void是一种意图上的约定,而不仅仅是特定值的限制。
总结:
如果一个函数返回类型为void,那么:
从语法上讲:函数是可以返回undefined的,至于显式返回,还是隐式返回,这无所谓!
从语义上讲:函数调用者不应关心函数返回的值,也不应依赖返回值进行任何操作!即使我们知道它返回了undefined。
5.object object与Object
object:所有非原始类型,可存储对象,函数,数组等。
Object:所有可以调用 Object方法的类型,即除了undefined和null的任何值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 let a :object a = {} a = {name :'张三' } a = [1 ,3 ,5 ,7 ,9 ] a = function ( ){} a = new String ('123' )class Person {} a = new Person () a = 1 a = true a = '你好' a = null a = undefined let b :Object b = {} b = {name :'张三' } b = [1 ,3 ,5 ,7 ,9 ] b = function ( ){} b = new String ('123' )class Person {} b = new Person () b = 1 b = true b = '你好' b = null b = undefined
声明类型 声明对象类型
对象类型字面量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let person1 : { name : string , age ?: number }let person2 : { name : string ; age ?: number }let person3 : {name : string age ?: number } person1 = {name :'李四' ,age :18 } person2 = {name :'张三' } person3 = {name :'王五' } person3 = {name :'王五' ,gender :'男' }
索引签名:允许定义对象可以具有任意数量 的属性,这些属性的键和类型是可变的 。常用于:描述类型不确定的属性,(具有动态属性的对象)。
1 2 3 4 5 6 7 8 9 10 11 12 13 量、任意类型的其他属性let person : {name : string age ?: number [key : string ]: any } person = {name :'张三' ,age :18 ,gender :'男' }
声明函数类型 函数类型注解
1 2 3 4 let count : (a : number , b : number ) => number count = function (x, y ) {return x + y }
TypeScript 中的=>在函数类型声明时表示函数类型,描述其参数类型和返回类型。
JavaScript 中的=>是一种定义函数的语法,是具体的函数实现。
声明数组类型 1 2 3 4 let arr : string []let arr2 : Array <string >
6.tuple Tuple是一种特殊的数组类型,可以存储固定数量 的元素,并且每个元素的类型是已知 的且可以不同 。元组用于精确描述一组值的类型, ?表示可选元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 let arr1 : [string ,number ]let arr2 : [number ,boolean ?]let arr3 : [number ,...string []] arr1 = ['hello' ,123 ] arr2 = [100 ,false ] arr2 = [200 ] arr3 = [100 ,'hello' ,'world' ] arr3 = [100 ] arr1 = ['hello' ,123 ,false ]
7.enum enum:枚举,定义一组命名常量,它能增强代码的可读性,也让代码更好维护。也可以指定枚举成员的初始值,其后的成员值会自动递增。
1 2 3 4 5 6 enum Direction { Up , Down , Left , Right }
数字枚举 :数字枚举一种最常见的枚举类型,其成员的值会自动递增,且数字枚举还具备反向映射的特点,在下面代码的打印中,不难发现:可以通过值来获取对应的枚举成员名称 。好处是语义性更强,便于维护。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 enum Direction {Up ,Down ,Left ,Right }console .log (Direction ) console .log (Direction .Up )console .log (Direction [0 ])Direction .Up = 'shang'
字符串枚举 :同理,枚举成员的值是字符串
常量枚举: 常量枚举是一种特殊枚举类型,它使用 const关键字定义,在编译时会被内联,避免生成一些额外的代码,推荐使用。
何为编译时内联? 所谓“内联”其实就是 TypeScript 在编译时,会将枚举成员引用替换为它们的实际值,而不是生成额外的枚举对象。这可以减少生成JavaScript 代码量,并提高运行时性能。
1 2 3 4 5 6 7 8 9 10 11 enum Directions {Up ,Down ,Left ,Right }let x = Directions .Up ;"use strict" ;let x = 0 ;
8.type type:为任何类型创建别名,让代码更简洁,可读性更强;同时更方便的进行类型复用和扩展。
类型别名 :使用 type关键字定义, type后跟类型名称,即类型别名。
1 2 3 type num = number ;let price : num price = 100
联合类型 :联合类型是一种高级类型,它表示一个值可以是几种不同类型之一。
1 2 3 4 5 6 7 8 9 10 11 12 13 type Status = number | string type Gender = '男' | '女' function printStatus (status : Status ) {console .log (status); }function logGender (str :Gender ){console .log (str) }printStatus (404 );printStatus ('200' );printStatus ('501' );logGender ('男' )logGender ('女' )
交叉类型 :允许将多个类型合并为一个类型。合并后的类型将拥有所有被合并类型的成员。交叉类型通常用于对象类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 type Area = {height : number ; width : number ; };type Address = {num : number ; cell : number ; room : string ; };type House = Area & Address ;const house : House = {height : 180 ,width : 75 ,num : 6 ,cell : 3 ,room : '702' };
9.特殊情况 使用类型声明 限制函数返回值为void时, TypeScript并不会严格要求函数返回空。
为什么?
是为了确保如下代码成立,我们知道 Array.prototype.push 的返回值是一个数字,而 Array.prototype.forEach方法期望其回调的返回类型是void。
1 2 3 4 5 6 7 8 9 10 type LogFunc = () => void const f1 : LogFunc = () => { return 100 ; };const f2 : LogFunc = () => 200 ; const f3 : LogFunc = function ( ) { return 300 ; };
1 2 3 const src = [1 , 2 , 3 ];const dst = [0 ]; src.forEach ((el ) => dst.push (el))
10.属性修饰符
修饰符
含义
具体规则
public
公开的
可以被类内部、子类、类外部 访问
protected
受保护的
可以被类内部、子类 访问
private
私有的
可以被类内部 访问
readonly
只读属性
属性无法修改
11.抽象类 定义: 抽象类是一种无法被实例化的类,专门用来定义类的结构和行为,类中可以写抽象方法,也可以写具体实现。抽象类主要用来为其派生类提供一个基础结构,要求其派生类必须实现其中的抽象方法。
简单来说: 抽象类不能实例化,其意义是可以被继承,抽象类里可以有普通方法、也可以有抽象方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 abstract class Package { constructor ( public weight :number ){ } abstract calculate ():number printPackage ( ){ console .log (`包裹重量为:${this .weight} \n运费为:${this .calculate()} ` ) } }class StandardPackage extends Package { constructor ( weight :number , public unitPrice :number ){ super (weight) } calculate (): number { return (this .weight * this .unitPrice ) } }const pkg1 = new StandardPackage (10 ,5 ) pkg1.printPackage ()
总结:何时使用抽象类
定义通用接口 :为一组相关的类定义通用的行为(方法或属性)时。
提供基础实现: 在抽象类中提供某些方法或为其提供基础实现,这样派生类就可以继承这些实现。
确保关键实现: 强制派生类实现一些关键行为。
共享代码和逻辑: 当多个类需要共享部分代码时,抽象类可以避免代码重复。
12.interface interface:一种定义结构的方式,主要作用是为:类、对象、函数等规定一种契约,这样可以确保代码的一致性和类型安全,但要注意interface只能定义格式,不能包含任何实现 !
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 interface PersonInterface {name : string age : number speak (n : number ): void }class Person implements PersonInterface {constructor (public name : string ,public age : number ) { }speak (n : number ): void {for (let i = 0 ; i < n; i++) {console .log (`你好,我叫${this .name} ,我的年龄是${this .age} ` ); } } }const p1 = new Person ('tom' , 18 ); p1.speak (3 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 interface UserInterface {name : string readonly gender : string age ?: number run : (n : number ) => void }const user : UserInterface = {name : "张三" ,gender : '男' ,age : 18 ,run (n ) {console .log (`奔跑了${n} 米` ) } };
1 2 3 4 5 6 interface CountInterface { (a : number , b : number ): number ; }const count : CountInterface = (x, y ) => {return x + y }
接口继承: 使用extends关键字实现接口继承
接口自动合并
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 interface PersonInterface {name : string age : number }interface PersonInterface {speak (): void }class Person implements PersonInterface {name : string age : number constructor (name : string , age : number ) {this .name = namethis .age = age }speak ( ) {console .log ('你好!我是老师:' , this .name ) } }
何时使用接口 :
定义对象格式: 描述数据类型、API响应格式、配置对象……
类的契约: 规定一个类需要视线的哪些方法和属性
扩展已有接口: 一般用于扩展第三方库的类型
13.相似概念的区别 13.1 interface 与 type 的区别
相同点: interface和type 都可以用于定义对象结构,在定义对象结构时两者可以互换。
不同点:
interface:更专注于定义对象和类的结构,支持继承、合并。
type:可以定义类型别名 、联合类型 、交叉类型 ,但不支持继承和自动合并。
简言之,对于需要复杂继承和扩展的类型,推荐使用interface&extends实现继承。
13.2 interface与抽象类的区别
1. 接口:只能描述结构,不能有任何实现代码,一个类可以有多个接口。
2. 抽象类:既可以包含抽象方法,也可以包含具体方法,一个类只能继承一个抽象类。
八.泛型 定义: 泛型运行我们在定义函数,类或接口时,使用类型参数来表示未指定的类型,这些参数在具体使用时,才被指定具体的类型,泛型能让同一段代码适用于多种类型,同时仍然保持类型的安全性。
泛型函数 1 2 3 4 5 function logData<T>(data :T){ console .log (data) } logData<number >(100 ) logData<string >('Hi' )
注:泛型可以有多个
泛型接口 1 2 3 4 5 6 interface PersonInterface <T>{ name :string , age :number , extraInfo :T }let p :PersonInterface <string >
注:泛型接口本身也可以嵌套
泛型类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Person <T> {constructor (public name : string ,public age : number ,public extraInfo : T ) { }speak ( ) {console .log (`我叫${this .name} 今年${this .age} 岁了` )console .log (this .extraInfo ) } }const p1 = new Person <number >("tom" , 30 , 250 );type JobInfo = {title : string ;company : string ; }const p2 = new Person <JobInfo >("tom" , 30 , { title : '研发总监' , company : '发发发 科技公司' });
泛型约束 1 2 3 4 5 6 7 8 9 10 interface LengthInterface {length : number }function logPerson<T extends LengthInterface >(data : T): void {console .log (data.length ) } logPerson<string >('hello' )
九.类型声明文件 类型声明文件是 TypeScript 中的一种特殊文件,通常以.d.ts 作为扩展名。它的主要作用 是为现有的 JavaScript 代码提供类型信息,使得 TypeScript 能够在使用这些 JavaScript 库 或模块时进行类型检查和提示。
在浏览器引入脚本时,注明类型type:module
1 2 3 4 5 6 7 export function add (a, b ) {return a + b; }export function mul (a, b ) {return a * b; }