使用TypeScript已经有了一段时间,这的确是一个好东西,虽说在使用的过程中也发现了一些bug,不过都是些小问题,所以整体体验还是很不错的。

TypeScript之所以叫Type,和它的强类型是分不开的,这也是区别于JavaScript最关键的一点,类型的声明可以直接写在代码中,也可以单独写一个用来表示类型的描述文件*.d.ts

常用方式

首先在d.ts中是不会存在有一些简单的基本类型定义的(因为这些都是写在表达式、变量后边的,在这里定义没有任何意义),声明文件中定义的往往都是一些复杂结构的类型。

大部分语法都与写在普通ts文件中的语法一致,也是export后边跟上要导出的成员。

最简单的就是使用type关键字来定义:

type A = {                 // 定义复杂结构
b: number
c: string
} type Func = () => number // 定义函数 type Key = number | string // 多个类型

组合类型

以及在TypeScript中有着很轻松的方式针对type进行复用,比如我们有一个Animal类型,以及一个Dog类型,可以使用&来进行复用。

P.S> &符号可以拼接多个

type Animal = {
weight: number
height: number
} type Dog = Animal & {
leg: number
}

动态的 JSON 类型指定

如果我们有一个JSON结构,而它的key是动态的,那么我们肯定不能将所有的key都写在代码中,我们只需要简单的指定一个通配符即可:

type info = {
[k: string]: string | number // 可以指定多个类型
} const infos: info = {
a: 1,
b: '2',
c: true, // error 类型不匹配
}

以及在新的版本中更推荐使用内置函数Record来实现:

const infos: Record<string, string | number> = {
a: 1,
b: '2',
c: true, // error
}

获取变量的类型

假如我们有一个JSON对象,里边包含了nameage两个属性,我们可以通过一些TypeScript内置的工具函数来实现一些有意思的事情。

通过keyoftypeof组合可以得到我们想要的结果:

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

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定义也没有什么区别。

比如我们要导出一个存在nameage两个属性的对象:

// 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定义的方式来创建,避免一些可能存在的奇奇怪怪的问题。

接口声明的自动合并

因为interfaceTypeScript特有的,所以也会有一些有意思的特性,比如相同命名的interface会被自动合并:

interface PersonalIntl {
name: string
} interface PersonalIntl {
age: number
} class Personal implements PersonalIntl {
name = 'Niko'
age = 18
}

不要在 interface 中使用函数重载

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声明类型声明相关的目前就总结了这些比较常用的,欢迎小伙伴们进行补充。

在之前的版本中有存在modulenamespace的定义,但是目前来看,好像更推荐使用 ES-Modules 版本的 import/export来实现类似的功能,而非自定义的语法,所以就略过了这两个关键字相关的描述

官方文档中有针对如何编写声明文件的模版,可以参考:传送阵

参考资料

如何编写 Typescript 声明文件的更多相关文章

  1. TypeScript 如何编写类库声明文件 .d.ts

    TypeScript 如何编写类库声明文件 .d.ts how to write a d.ts file declaration-files/ https://www.typescriptlang.o ...

  2. JS如何捆绑TypeScript声明文件

    前话 TypeScript是JavaScript类型的超集 这是TypeScript的文档介绍的一句话,那么他们存在联系呢? 我的理解是,TypeScript在JavaScript基础上引入强类型语言 ...

  3. TypeScript声明文件

    为什么需要声明? 声明的本质是告知编译器一个标识符的类型信息.同时,在使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全.接口提示等功能. 声明在TypeScript中至关重要,只有通过 ...

  4. TypeScript完全解读(26课时)_20.声明文件

    首先学习识别已有的js库的类型 识别已有的js库的类型 UMD既可以作为全局库使用,也可以作为模块使用 先在着手来编写一个全局的库 新建文件 接收一个title,改变页面title的值 这里用到 &a ...

  5. TypeScript Writing .d.ts files(编写声明文件)

    当使用扩展的JavaScript库或者插件API的时候,将需要使用声明文件(.d.ts)来描述库的类型.本文内容将包括如何编写声明文件相关的一些高级概念,然后用一些例子来展示如何将各式各样的概念与声明 ...

  6. TypeScript 的声明文件的使用与编写

    https://fenying.net/2016/09/19/typings-for-typescript/ TypeScript 是 JavaScript 的超集,相比 JavaScript,其最关 ...

  7. TypeScript学习笔记(八) - 声明文件

    本篇将介绍TypeScript的声明文件,并简单演示一下如何编写和使用声明文件.本篇也是这个系列的最后一篇. 一.声明文件简介 TypeScript作为JavaScript的超集,在开发过程中不可避免 ...

  8. TypeScript 之 声明文件的使用

    https://www.tslang.cn/docs/handbook/declaration-files/consumption.html 下载 在TypeScript 2.0以上的版本,获取类型声 ...

  9. TypeScript 之 声明文件的发布

    https://www.tslang.cn/docs/handbook/declaration-files/publishing.html 发布声明文件到npm,有两种方式: 与你的npm包捆绑在一起 ...

随机推荐

  1. [转帖漫画]什么是 MD5 算法?---摘要算法 具体算法 一直搞不清楚

    漫画:什么是 MD5 算法? 2017-10-05 20:16 来源:伯乐专栏作者/玻璃猫,微信公众号 - 梦见 好文投稿, 请点击 → 这里了解详情 梦见 摘要哈希生成的正确姿势是什么样呢?分三步: ...

  2. php父级目录文件包函问题

    问题: php子目录不能包函父目录中的文件. 环境: 网站根目录:/var/www/html/ PHP版本: 5.3.3 Apache版本:2.2 好了,创建三个文件: //文件路径:/var/www ...

  3. Redis 优势以及性能问题

      1. 使用redis有哪些好处? (1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1) (2) 支持丰富数据类型,支持string, ...

  4. 浅谈C++/JAVA/C#运行机制和执行效率

    估计有很多同学都对C++/JAVA/C#这三大热门语言的运行机制和执行效率有或多或少的困惑,自己也有,但是经过前期的学习,了解了三者在这两方面的区别,就废话不说了,进入主题吧.         一.运 ...

  5. python自动化之正则

    import re phoneNumRegex=re.compile(r'\d\d\d-\d\d\d-\d\d\d\d') mo=phoneNumRegex.search('My number is ...

  6. Nim游戏学习笔记

  7. 【uoj#209】[UER #6]票数统计 组合数+乱搞

    题目描述 一个长度为 $n$ 的序列,每个位置为 $0$ 或 $1$ 两种.现在给出 $m$ 个限制条件,第 $i$ 个限制条件给出 $x_i$ .$y_i$ ,要求至少满足以下两个条件之一: 序列的 ...

  8. BZOJ5254 FJWC2018红绿灯(线段树)

    注意到一旦在某个路口被红灯逼停,剩下要走的时间是固定的.容易想到预处理出在每个路口被逼停后到达终点的最短时间,这样对于每个询问求出其最早在哪个路口停下就可以了.对于预处理,从下一个要停的路口倒推即可. ...

  9. Java IO流学习总结 - BIO

      Java流操作有关的类或接口: Java流类图结构: 流的概念和作用 流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象.即数据在两设备间的传输称为流,流的本质是数据传输,根据数据 ...

  10. Extend to Palindrome UVA - 11475(补成回文串)

    题意: 就是用最少的字符把原字符串补成回文串 解析: emm/.../网上都是用kmp和后缀数组做的 我没想到这俩的思路...emmm... 想到了exkmp的  就是原串和逆串匹配一下  注意要保证 ...