在分享之前我先回答一下为什么要使用 TypeScript(以下简称:TS)?前端的 TS 是什么?我们如何在项目中更好的使用 TS?
概念 TS 是什么?
首先 TS 是 JavaScript(以下简称:JS)的一个超集。我这里先回顾一下高中的超集 概念:
如果一个集合 S2 中的每一个元素都在集合 S1 中,且集合 S1 中可能包含 S2 中没有的元素,则集合 S1 就是 S2 的一个超集,反过来,S2 是 S1 的子集。 S1 是 S2 的超集,若 S1 中一定有 S2 中没有的元素,则 S1 是 S2 的真超集,反过来 S2 是 S1 的真子集。
这里的意思就是 TS 包含了所有的 JS 特性,并且拥有一些 JS 里面没有的特性,能够编译成 JavaScript 代码。其中最大的特性就是在JS 中变量是没有类型的,只有值是有类型的 ,TS 中变量也具有类型 ,其核心能力是在代码编写过程中提供了类型支持,以及在编译过程中进行类型校验。
前端的 TS 是什么?
在前端业务中使用 TS 更多的在于对类型的规定,某些高级的 TS 特性(比如抽象类、命名空间)使用场景较少。即使是泛型 这块使用的最多场景也就在前后端接口请求这块。
为什么要使用 TS?
可能有些人觉得写 TS 要写很多类型上面的代码,加大了前端的开发量,其实不然,在我看来动态语言的最大的弊端就是调试困难 ,因为动态语言的变量是无类型 的,变量指向的内容就不确定,变量的引用只有在运行时 才知道它具体指向哪里,才知道这个变量包含哪些内容。如果变量有类型,不用等到运行期间,在编码的时候就知道某个对象是不是我们期待的那个对象;其次,在后期迭代的时候回顾先前的代码也比较方便,当项目有新人到来的时候也更方便新人了解项目。前期的小投入可以换来后期大回报。最后 TS 的是 JS 的一个超集,对于前端工程师来说学习成本很低。
我们如何在项目中更好的使用 TS?
怎么叫用的好?其实很简单,当我们拿到一个变量时,我可以很清楚的知道这个变量是什么,它指向哪,如果他是高级类型,我们在`vs code
或者其他IDE
的时候能智能提示他有什么属性或者方法。
在编译 TS
时,即使报错,默认也不会中断编译 基本类型 基本类型和 JS 类似,多了几个特有的enum,unknown,void,never,tuple,any
对于any
和unknown
,两者最大的区别是,unknown
为任何类型的父类型,any
既是任何类型的父类型,也是任何类型的子类型
即任意类型可以转换为 unknown
。
鸭子类型 在程序设计中,鸭子类型 (英语:duck typing)是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定。
“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。
TypeScript 采用了所谓的 “鸭子类型”策略。当两个类型具有相同的属性以及方法时,它们就可以看作是同一类型。
如何定义一个变量 1 2 3 4 5 6 7 8 9 10 11 typedef struct _Point { int x; int y; } Point; int main () { Point p; p.x = 1 ; p.y = 2 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 struct Point { int x; int y; }; struct Person { int age; }; struct Student : public Person { Student () { this ->age = 18 ; } char name[20 ]; }; int main () { Point p; p.x = 1 ; p.y = 2 ; Person *person = new Student (); }
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 class Point { int x; int y; } interface Person { String: name; } class Student implements Person { private String name; Student() { this .name = "" } } public class Main { public static void main (String[] args) { Point p = new Point (); p.x = 1 ; p.y = 2 ; Person person = new Student (); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 interface Point { x : number y : number } interface XPoint { x : number y : number } function fn (p: Point ) { console .log (p.x , p.y ) } const p : XPoint = { x : 100 , y : 100 , } fn (p)
接口 interface 用来声明类型和接口的
TS
在前端的接口可能又拥有另外一重意思,在其他静态类型语言里面,接口是一种抽象类型,它用来制定一些规范,你要实现我的接口,就必须遵循我的规定。
在其它静态语言中(比如 Java,C#)的接口的特性,TS 里面也有,但在前端(浏览器层)的 TS 里面,接口interface
更多的是用来表示某一个变量它具体有哪些内容。多态的这一特性很少有使用到。
type
和interface
type 类型别名可以用来给已有的类型取一个别名 只能声明一次,多次声明同一种类型报错 interface 接口/类型定义 type
和interface
的区别在于,能用interface
表示的地方,一定能用type
表示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 interface BaseUser { readonly id : number } interface BaseUser { name : string } type BaseUser = { readonly id : number }
属性修饰 1 2 3 4 5 6 7 8 9 10 interface Point { x : number y?: number }
类型断言/强制类型转换
类型断言,这里我习惯称之为强制类型转换。
```typescript interface Point { x: number y: number } // 报错,因为ts认为{}是一个对象,他没有x,y这两属性,就不能赋值给p const p: Point = {} // 即使{}没x,y 这里通过断言的形式告诉编译器{}它的值就是一个Point类型的值 const p1: Point = {} as Point const p2: Point ={}1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 **可以索引类型** ```typescript // 索引类型 // 类型限定 interface Person { // 属性扩充,表示Person类型的属性只能是number 或者 string类型的,并且可以添加未在内部声明的字段 [propName: string]: number | string age: number name: string address: string } const p: Person = { age: 18, name: 'Pic', address: '', // 原定义中没有,但[propName: string]: number | string 表示可以进行属性动态添加,只要满足属性为number或者string zipCode: '111', } p.gender = 0
函数类型
1 2 3 4 5 6 7 8 interface ScrollDebounceFunction { (event : Event ): void cancel : () => void } type ScrollDebounceFunction = (event: Event ) => void
继承
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 interface BaseUser { readonly id : number } interface User extends BaseUser { username : string | undefined address?: string } interface Base1 { age : number } interface Base2 { id : string address : string } interface Person extends Base1 , Base2 { name : string }
在多继承时,会继承所有父级的属性,当多个父级有相同的属性,但属性的只读、可选限定不同时,会继承失败,报错。当父子都有相同的属性时,子属性与父属性的类型不同时,类型相同时,以子属性的只读限定为准。但当属性为必选限制,子属性就不能改成可选限定。
泛型 我理解的泛型是为了增强类型的可复用性,在定义某一类型时,某个属性的具体类型不确定,但这个类型的基本结构可预估。
内置基础泛型 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 interface Admin { name : string age : number address : string role : string } type Partial <T> = { [P in keyof T]?: T[P] } type A = Partial <Admin >type Required <T> = { [P in keyof T]-?: T[P] } type Readonly <T> = { readonly [P in keyof T]: T[P] } type Pick <T, K extends keyof T> = { [P in K]: T[P] } type Parameters <T extends (...args : any ) => any > = T extends ( ...args : infer P ) => any ? P : never type ConstructorParameters <T extends new (...args : any ) => any > = T extends new (...args : infer P) => any ? P : never type ReturnType <T extends (...args : any ) => any > = T extends ( ...args : any ) => infer R ? R : any type InstanceType <T extends new (...args : any ) => any > = T extends new ( ...args : any ) => infer R ? R : any interface ThisType <T> {}
几个解释
上面的T
称之为 类型变量 ,它是一种特殊的变量,只用于表示类型而不是值。
extends
当extends
在形参列表里面时,表示的对传入的类型 T 的泛型约束。T extends (...args: any) => any
这里对 T 的限制就是T
只能是函数类型,
infer
代表推理。并且只能的 extends 一起用
这里详细说明一下 infer 的作用
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 57 58 59 60 61 62 63 type AMoreThanB = (a: number , b: number ) => boolean type ReturnType <T extends AMoreThanB > = T extends AMoreThanB ? boolean : any type Rt = ReturnType <AMoreThanB >type Rt2 = ReturnType <boolean >type ReturnType <T extends (...arg : any ) => any > = T extends AMoreThanB ? boolean : any type TimeoutFuc = (callback: AMoreThanB, delay: number ) => number type Rt3 = ReturnType <TimeoutFuc >type ReturnType <T extends (...arg : any ) => any > = T extends (...arg : any ) => any ? boolean : any type ReturnType <T extends (...arg : any ) => any > = T extends ( ...arg : any ) => infer R ? R : any type Rt4 = ReturnType <TimeoutFuc >type ReturnType <T extends (...arg : any ) => any > = T extends ( arg1 : infer T, ...arg : any ) => infer R ? T | R : any type Rt5 = ReturnType <TimeoutFuc >
类 继承,接口,多态就不讲了。
讲一下类的装饰器,装饰器可以对原有的类,或者属性,方法,在编译的时候 进行加强。
执行顺序 属性装饰器、参数装饰器、方法装饰器、类装饰器
类装饰器是作用于当前这个类,属性装饰器、方法装饰器、参数装饰器
作用于这个类的 prototype
执行时机 编译时
属性装饰器 1 2 3 function fieldDescriptor (target: any , fieldName: string ): void { console .log ('fieldDescriptor' , typeof prototype, prototype) }
参数装饰器 1 2 3 4 5 6 7 function paramsDescriptor ( target: any , propertyKey: string , parameterIndex: number ): any { console .log ('paramsDescriptor' , propertyKey, parameterIndex) }
方法装饰器 1 2 3 function methodDescriptor (prototype: Demo, methodName: string ): void { console .log ('methodDescriptor' , typeof prototype, prototype) }
类装饰器 1 2 3 function classDescriptor (constructor: any ): void { console .log ('classDescriptor' , typeof constructor, constructor) }
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 function fieldDescriptor (target: any , fieldName: string ): void { console .log ('fieldDescriptor' , typeof target, target) } function paramsDescriptor ( target: any , propertyKey: string , parameterIndex: number ): any { console .log ('paramsDescriptor' , propertyKey, parameterIndex) } function methodDescriptor (prototype: Demo, methodName: string ): void { console .log ('methodDescriptor' , typeof prototype, prototype) } function classDescriptor (constructor: any ): void { console .log ('classDescriptor' , typeof constructor, constructor) } @classDescriptor class Person { @fieldDescriptor name : string @methodDescriptor log (@paramsDescriptor p: string ) { console .log (this .name ) } }
编译后 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 var __decorate = (this && this .__decorate ) || function (decorators, target, key, desc ) { var c = arguments .length , r = c < 3 ? target : desc === null ? (desc = Object .getOwnPropertyDescriptor (target, key)) : desc, d if (typeof Reflect === 'object' && typeof Reflect .decorate === 'function' ) r = Reflect .decorate (decorators, target, key, desc) else for (var i = decorators.length - 1 ; i >= 0 ; i--) if ((d = decorators[i])) r = (c < 3 ? d (r) : c > 3 ? d (target, key, r) : d (target, key)) || r return c > 3 && r && Object .defineProperty (target, key, r), r } var __param = (this && this .__param ) || function (paramIndex, decorator ) { return function (target, key ) { decorator (target, key, paramIndex) } } function fieldDescriptor (target, fieldName ) { console .log ('fieldDescriptor' , typeof target, target) } function paramsDescriptor (target, propertyKey, parameterIndex ) { console .log ('paramsDescriptor' , propertyKey, parameterIndex) } function methodDescriptor (prototype, methodName ) { console .log ('methodDescriptor' , typeof prototype, prototype) } function classDescriptor (constructor ) { console .log ('classDescriptor' , typeof constructor, constructor) } var Person = (function ( ) { function Person ( ) {} Person .prototype .log = function (p ) { console .log (this .name ) } __decorate ([fieldDescriptor], Person .prototype , 'name' ) __decorate ( [methodDescriptor, __param (0 , paramsDescriptor)], Person .prototype , 'log' ) Person = __decorate ([classDescriptor], Person ) return Person })()