「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战」
TypeScript的普及以及很多年了,相信大家在日常的工作中也开始广泛使用了,但是,我发现很多TS项目,其实就是把JS文件后缀改成了TS,根本没有发挥TypeScript强大的类型体系。在项目中充斥着大量的any和断言,这和写JS能有啥区别呢?毕竟TypeScript的类型声明会在编译成JS时被剔除。
为什么要这么强调使用TypeScript的类型系统?
这个是一个好的问题,也是勾起认真
使用TypeScript类型的良好开端。
类型具有提高代码质量和可理解性的成熟能力,比较官方的理由:
- 类型在进行重构时可以提高敏捷性。对于编译器来说,捕获错误比使事情在运行时失败更好;
- 类型是您可以拥有的最佳文档形式之一。函数签名是一个定理,函数体是证明
举个例子,我们在平时的日常开发中,总需要对接后端提供的API吧,然后把返回的数据绑定到视图层,反正现代的前端模式是这样的。但是,项目上线后一段时间,后端的API字段修改了,如果没有类型定义,我们需要人工去翻阅之前的逻辑,调试,如果涉及的组件较多,可能就会漏改。针对这一局面,类型系统可以帮助我们进行快递校验,在API返回结构的源头处修改数据结构,编译时即可抛出错误。
如何写好一个类型定义?
我们先来看一个简单的例子,定义一些简单的动物类:
interface Animal {
color: string;
age: number;
sex: string;
}
interface Cat extends Animal {
sleep: () => void;
}
interface Dog extends Animal {
eat: () => void;
}
然后我们期望他们去做一些动作,但是,Cat只能sleep,Dog只能eat,那么代码会是这样的:
function catAction(animal: Cat, sleep: () => void): Cat {
Object.assign(animal, {
sleep,
})
return animal
}
function dogAction(animal: Dog, eat: () => void): Dog {
Object.assign(animal, {
sleep,
})
return animal
}
是否注意到了重复的地方,因为都继承自同一个类,那么,我们就可以利用泛型来重新实现一遍:
function Action<T extends Animal>(animal: T, opts: Object): T {
Object.assign(animal, opts)
return animal
}
在上面这个非常简单的例子里,通过 T 这个类型变量,我们不仅在输入参数和返回值的类型之间建立了动态的联系,还在输入参数之间建立了约束,这是一种很强大的表达力。另外由于这类变量在语义上几乎总是相当于占位符,所以我们一般会把它们简写成 T / U / V 之类。
但是,是否发现我们第二个参数好像不够严谨,因为可以传入一些不符合类型定义的数据进入,我们再改进一下:
function Action<T extends Animal>(animal: T, opts: Record<string, () => void>): T {
Object.assign(animal, opts)
return animal
}
利用了Record辅助类,强制约束K-V的类型,TypeScript 还内置了很多这样的辅助类型,参见 Utility Types。
但是,这还是不够,那么,就有必要上升到类型体操的级别了,像这样:
function Action<T extends Animal, U extends { [K in keyof T]: T[K] }>(animal: T, opts: U): T {
Object.assign(animal, opts)
return animal
}
// 如果觉得每次并不是所有参数都需要给的,而是部分,那么我们可以再次借助辅助类:
function Action<T extends Animal, U extends Partial<{ [K in keyof T]: T[K] }>>(animal: T, opts: U): T {
Object.assign(animal, opts)
return animal
}
const cat: Cat = {
color: 'white',
age: 3,
sex: 'girl',
sleep: () => true,
}
Action(cat, {
sleep: () => true,
})
这样,严谨多了,通过寥寥几行类型空间的代码,就能借助 TypeScript 类型检查器的威力,将原本需要放在运行时的校验逻辑直接优化到在编译期完成。
小结
利用好TypeScript的类型系统,不仅可以帮助我们提高开发效率,还提高了整体的开发质量。
当然,在一些业务逻辑比较简单的项目中,还是不怎么推荐大量用TypeScript的类型系统,原因有下:
- 逻辑简单,TypeScript的类型系统是加强逻辑严谨性,都没有什么逻辑,怎么加强?
- 类型定义是有额外的工作量的,是用现在的时间换取未来的稳定性以及重构所消耗的时候,如果未来的修改时间只有短短一个变量替换动作,那还值得吗?