使用TypeScript
已经有了一段时间,这的确是一个好东西,虽然说在使用的过程当中也发现了一些bug
,不过都是些小问题,因此总体体验仍是很不错的。javascript
TypeScript
之因此叫Type
,和它的强类型是分不开的,这也是区别于JavaScript
最关键的一点,类型的声明能够直接写在代码中,也能够单独写一个用来表示类型的描述文件*.d.ts
。html
首先在d.ts
中是不会存在有一些简单的基本类型定义的(由于这些都是写在表达式、变量后边的,在这里定义没有任何意义),声明文件中定义的每每都是一些复杂结构的类型。java
大部分语法都与写在普通ts
文件中的语法一致,也是export
后边跟上要导出的成员。typescript
最简单的就是使用type
关键字来定义:json
type A = { // 定义复杂结构 b: number c: string } type Func = () => number // 定义函数 type Key = number | string // 多个类型
以及在TypeScript
中有着很轻松的方式针对type
进行复用,好比咱们有一个Animal
类型,以及一个Dog
类型,可使用&
来进行复用。app
P.S> &
符号能够拼接多个函数
type Animal = { weight: number height: number } type Dog = Animal & { leg: number }
若是咱们有一个JSON
结构,而它的key
是动态的,那么咱们确定不能将全部的key
都写在代码中,咱们只须要简单的指定一个通配符便可:工具
type info = { [k: string]: string | number // 能够指定多个类型 } const infos: info = { a: 1, b: '2', c: true, // error 类型不匹配 }
以及在新的版本中更推荐使用内置函数Record
来实现:ui
const infos: Record<string, string | number> = { a: 1, b: '2', c: true, // error }
假如咱们有一个JSON对象,里边包含了name
、age
两个属性,咱们能够经过一些TypeScript
内置的工具函数来实现一些有意思的事情。this
经过keyof
与typeof
组合能够获得咱们想要的结果:
const obj = { name: 'Niko', age: 18 } // 若是是这样的取值,只能写在代码中,不能写在 d.ts 文件中,由于声明文件里边不能存在实际有效的代码 type keys = keyof typeof obj let a: keys = 'name' // pass let b: keys = 'age' // pass let c: keys = 'test' // error
而若是咱们想要将一个类型不统一的JSON
修改成统一类型的JSON
也可使用这种方式:
const obj = { name: 'Niko', age: 18, birthday: new Date() } const infos: Record<keyof typeof obj, string> = { name: '', age: '', birthday: 123, // 出错,提示类型不匹配 test: '', // 提示不是`info`的已知类型 }
又好比说咱们有一个函数,函数会返回一个JSON
,而咱们须要这个JSON
来做为类型。
那么能够经过ReturnType<>
来实现:
function func () { return { name: 'Niko', age: 18 } } type results = ReturnType<typeof func> // 或者也能够拼接 keyof 获取全部的 key type resultKeys = keyof ReturnType<typeof func> // 亦或者能够放在`Object`中做为动态的`key`存在 type infoJson = Record<keyof ReturnType<typeof func>, string>
class
类型由于咱们知道函数和class
在建立的时候是都有实际的代码的(函数体、构造函数)。
可是咱们是写在d.ts
声明文件中的,这只是一个针对类型的约束,因此确定是不会存在真实的代码的,可是若是在普通的ts
文件中这么写会出错的,因此针对这类状况,咱们须要使用declare
关键字,表示咱们这里就是用来定义一个类型的,而非是一个对象、函数:
class Personal { name: string // ^ 出错了,提示`name`必须显式的进行初始化 } function getName (personal: Personal): name // ^ 出错了,提示函数缺失实现
如下为正确的使用方式:
-declare class Personal { +declare class Personal { name: string } -function getName (personal: Personal): name +declare function getName (personal: Personal): name
固然了,通常状况下是不建议这么定义class
的,应该使用interface
来代替它,这样的class
应该仅存在于针对非TS
模块的描述,若是是本身开发的模块,那么自己结构就具备声明类型的特性。
这个概念是在一些强类型语言中才有的,依托于TypeScript
,这也算是一门强类型语言了,因此就会有须要用到这种声明的地方。
例如咱们有一个add
函数,它能够接收string
类型的参数进行拼接,也能够接收number
类型的参数进行相加。
须要注意的是,只有在作第三方插件的函数重载定义时可以放到d.ts
文件中,其余环境下建议将函数的定义与实现放在一块儿(虽然说配置paths
也可以实现分开处理,可是那样就失去了对函数建立时的约束)
// index.ts // 上边是声明 function add (arg1: string, arg2: string): string function add (arg1: number, arg2: number): number // 由于咱们在下边有具体函数的实现,因此这里并不须要添加 declare 关键字 // 下边是实现 function add (arg1: string | number, arg2: string | number) { // 在实现上咱们要注意严格判断两个参数的类型是否相等,而不能简单的写一个 arg1 + arg2 if (typeof arg1 === 'string' && typeof arg2 === 'string') { return arg1 + arg2 } else if (typeof arg1 === 'number' && typeof arg2 === 'number') { return arg1 + arg2 } }
TypeScript
中的函数重载也只是多个函数的声明,具体的逻辑还须要本身去写,他并不会真的将你的多个重名 function 的函数体进行合并
想象一下,若是咱们有一个函数,传入Date
类型的参数,返回其unix
时间戳,若是传入Object
,则将对象的具体类型进行toString
输出,其他状况则直接返回,这样的一个函数应该怎么写?
仅作示例演示,通常正常人不会写出这样的函数...
function build (arg: any) { if (arg instanceof Date) { return arg.valueOf() } else if (typeof arg === 'object') { return Object.prototype.toString.call(arg) } else { return arg } }
可是这样的函数重载在声明的顺序上就颇有讲究了,必定要将精确性高的放在前边:
// 这样是一个错误的示例,由于不管怎样调用,返回值都会是`any`类型 function build(arg: any): any function build(arg: Object): string function build(arg: Date): number
由于TypeScript
在查找到一个函数重载的声明之后就会中止不会继续查找,any
是一个最模糊的范围,而Object
又是包含Date
的,因此咱们应该按照顺序从小到大进行排列:
function build(arg: Date): number function build(arg: Object): string function build(arg: any): any // 这样在使用的时候才能获得正确的类型提示 const res1 = build(new Date()) // number const res2 = build(() => { }) // string const res3 = build(true) // any
函数重载的意义在于可以让你知道传入不一样的参数获得不一样的结果,若是传入的参数不一样,可是获得的结果(类型)却相同,那么这里就不要使用函数重载(没有意义)。
若是函数的返回值类型相同,那么就不须要使用函数重载
function func (a: number): number function func (a: number, b: number): number // 像这样的是参数个数的区别,咱们可使用可选参数来代替函数重载的定义 function func (a: number, b?: number): number // 注意第二个参数在类型前边多了一个`?` // 亦或是一些参数类型的区别致使的 function func (a: number): number function func (a: string): number // 这时咱们应该使用联合类型来代替函数重载 function func (a: number | string): number
interface
是在TypeScript
中独有的,在JavaScript
并无interface
一说。
由于interface
只是用来规定实现它的class
对应的行为,没有任何实质的代码,对于脚本语言来讲这是一个无效的操做
在语法上与class
并无什么太大的区别,可是在interface
中只可以进行成员属性的声明,例如function
只可以写具体接收的参数以及返回值的类型,并不可以在interface
中编写具体的函数体,一样的,针对成员属性也不可以直接在interface
中进行赋值:
// 这是一个错误的示例 interface PersonalIntl { name: string = 'Niko' sayHi (): string { return this.name } } // 在 interface 中只能存在类型声明 interface PersonalIntl { name: string sayHi (): string }
其实在一些状况下使用interface
与普通的type
定义也没有什么区别。
好比咱们要导出一个存在name
和age
两个属性的对象:
// types/personal.d.ts export interface PersonalIntl { name: string age: number } // index.d.ts import { PersonalIntl } from './types/personal' const personal: PersonalIntl = { name: 'Niko', age: 18, }
若是将interface
换成type
定义也是彻底没问题的:
// types/personal.d.ts export type PersonalIntl = { name: string age: number }
这样的定义在基于上边的使用是彻底没有问题的,可是这样也仅仅适用于Object
字面量的声明,没有办法很好的约束class
模式下的使用,因此咱们采用interface
来约束class
的实现:
import { PersonalIntl } from './types/personal' class Personal implements PersonalIntl { constructor(public name: string, public age: number) { } // 上边的简写与下述代码效果一致 public name: string public age: number constructor (name: string, age: number) { this.name = name this.age = age } } const personal = new Personal('niko', 18)
首先,在接口中有两种方式能够定义一个函数,一个被定义在实例上,一个被定义在原型链上。
两种声明方式以下:
interface PersonalIntl { func1 (): any // 原型链方法 func2: () => any // 实例属性 }
可是咱们在实现这两个属性时实际上是能够互相转换的,并无强要求必须使用哪一种方式:
class Personal implements PersonalIntl { func1 () { console.log(this) } func2 = () => { console.log(this) } }
其实这二者在编译后的JavaScript
代码中是有区别的,并不清楚这是一个bug
仍是设计就是如此,相似这样的结构:
var Personal = /** @class */ (function () { function Personal() { var _this = this; this.func2 = function () { console.log(_this); }; } Personal.prototype.func1 = function () { console.log(this); }; return Personal; }());
因此在使用的时候仍是建议最好按照interface
定义的方式来建立,避免一些可能存在的奇奇怪怪的问题。
由于interface
是TypeScript
特有的,因此也会有一些有意思的特性,好比相同命名的interface
会被自动合并:
interface PersonalIntl { name: string } interface PersonalIntl { age: number } class Personal implements PersonalIntl { name = 'Niko' age = 18 }
在interface
中使用函数重载,你会获得一个错误的结果,仍是拿上边的build
函数来讲,若是在interface
中声明,而后在class
中实现,那么不管怎样调用,返回值的类型都会认为是any
。
因此正确的作法是在class
中声明重载,在class
中实现,interface
中最多只定义一个any
,而非三个重载。
class Util implements UtilIntl { build(arg: Date): number build(arg: Object): string build(arg: any): any build(arg: any) { if (arg instanceof Date) { return arg.valueOf() } else if (typeof arg === 'object') { return Object.prototype.toString.call(arg) } else { return arg } } }
有关TypeScript
声明类型声明相关的目前就总结了这些比较经常使用的,欢迎小伙伴们进行补充。
在以前的版本中有存在module
和namespace
的定义,可是目前来看,好像更推荐使用 ES-Modules 版本的 import
/export
来实现相似的功能,而非自定义的语法,因此就略过了这两个关键字相关的描述
官方文档中有针对如何编写声明文件的模版,能够参考:传送阵