摘要:函数重载这个概念是在一些强类型语言中才有的,依托于,这也算是一门强类型语言了,所以就会有需要用到这种声明的地方。
使用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获取变量的类型= { a: 1, b: "2", c: true, // error }
假如我们有一个JSON对象,里边包含了name、age两个属性,我们可以通过一些TypeScript内置的工具函数来实现一些有意思的事情。
通过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获取函数的返回值类型= { name: "", age: "", birthday: 123, // 出错,提示类型不匹配 test: "", // 提示不是`info`的已知类型 }
又比如说我们有一个函数,函数会返回一个JSON,而我们需要这个JSON来作为类型。
那么可以通过ReturnType<>来实现:
function func () { return { name: "Niko", age: 18 } } type results = ReturnType在代码中声明函数和class类型// 或者也可以拼接 keyof 获取所有的 key type resultKeys = keyof ReturnType // 亦或者可以放在`Object`中作为动态的`key`存在 type infoJson = Record , string>
因为我们知道函数和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): numberInterface
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 中使用函数重载
在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来实现类似的功能,而非自定义的语法,所以就略过了这两个关键字相关的描述
官方文档中有针对如何编写声明文件的模版,可以参考:传送阵
参考资料keyof
Record
ReturnType 及其他的内置函数
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/98380.html
摘要:迁移至指南为什么要迁移至本身是动态弱类型的语言,这样的特点导致了代码中充斥着很多的报错,给开发调试和线上代码稳定都带来了不小的负面影响。可行性因为是的超集,不会阻止的运行,即使存在类型错误也不例外,这能让你的逐步迁移至。 Vue2.5+迁移至Typescript指南 为什么要迁移至Typescript Javascript本身是动态弱类型的语言,这样的特点导致了Javascript代码...
摘要:迁移至指南为什么要迁移至本身是动态弱类型的语言,这样的特点导致了代码中充斥着很多的报错,给开发调试和线上代码稳定都带来了不小的负面影响。可行性因为是的超集,不会阻止的运行,即使存在类型错误也不例外,这能让你的逐步迁移至。 Vue2.5+迁移至Typescript指南 为什么要迁移至Typescript Javascript本身是动态弱类型的语言,这样的特点导致了Javascript代...
摘要:单元测试一个合格的库应该包含完整的单元测试。是的支持版,和是一样的,它能够直接运行为后缀的单元测试文件。在目录下加入然后执行即可看到单元测试结果。 这篇文章主要是讲述如何使用 TypeScript 编写一个完善,包含测试、文档、持续集成的库,涵盖了编写整个库所需要的技术和工具,主要涵盖: 项目目录骨架 TypeScript 配置 使用 jest 单元测试 使用 vuepress 编写...
摘要:接下来来看一段代码示例语法与语言比较当类型不对的时候,会提示错误编译后语法联想大致可以把它看成是加了类型系统的。 一篇文章学会 TypeScript (内部分享标题:TypeScript 基础) 这篇文章是我在公司前端小组内部的演讲分享稿,目的是教会大家使用 TypeScript,这篇文章虽然标着基础,但我指的基础是学完后就能够胜任 TypeScript 的开发工作。从我分享完的效果来...
阅读 2873·2021-11-23 09:51
阅读 3390·2021-11-22 09:34
阅读 3270·2021-10-27 14:14
阅读 1474·2019-08-30 15:55
阅读 3329·2019-08-30 15:54
阅读 1047·2019-08-30 15:52
阅读 1864·2019-08-30 12:46
阅读 2828·2019-08-29 16:11