
最近,在研究家庭项目操作不正确的原因时,我再次注意到由于疲劳而经常重复发生的错误。 错误的本质在于,在一个代码块中有多个标识符,当我调用函数时,我传递了另一种类型的对象的标识符。 在本文中,我将讨论如何使用TypeScript解决此问题。
一点理论
TypeScript基于结构化类型,非常适合JavaScript的鸭子思想。 关于这一点的文章已经足够多了。 我不会重复它们,我只会概述与名词性输入的主要区别,后者在其他语言中更常见。 让我们来看一个小例子。
class Car { id: number; numberOfWheels: number; move (x: number, y: number) {
为什么TypeScript会有这种行为? 这只是结构化类型的体现。 与监视类型名称的名词性不同,结构化类型根据类型的内容决定类型的兼容性。 Car类包含Boat类的所有属性和方法,因此Car可以用作Boat。 相反,这是不正确的,因为Boat不具有numberOfWheels属性。
键入标识符
首先,我们将设置标识符的类型
type CarId: number; type BoatId: number;
并使用这些类型重写类。
class Car { id: CarId; numberOfWheels: number; move (x: number, y: number) {
您会注意到情况并没有太大变化,因为我们仍然无法控制从何处获取标识符,您将是对的。 但是这个例子已经提供了一些优点。
在程序开发过程中,标识符的类型可能会突然改变。 因此,例如,可以用一个字符串VIN号代替该项目独有的某个车号。 如果不指定标识符的类型,则必须在所有出现数字的地方都用字符串替换数字。 对于类型任务,只需要在确定类型本身的一个地方进行更改。
调用函数时,我们会从代码编辑器获得提示,即应该使用什么类型标识符。 假设我们声明了以下函数:
function getCarById(id: CarId): Car {
然后,编辑器会提示我们不仅必须传送数字,还必须传送CarId或BoatId。
模拟最严格的打字
TypeScript中没有名义上的类型,但是我们可以模拟其行为,使任何类型都是唯一的。 为此,将唯一的属性添加到类型。 该技巧在英文文章的“品牌”一词中进行了介绍,其外观如下:
type BoatId = number & { _type: 'BoatId'}; type CarId = number & { _type: 'CarId'};
指出我们的类型必须既是数字又是具有唯一值的具有属性的对象,我们在理解结构类型时使我们的类型不兼容。 让我们看看它是如何工作的。
let carId: CarId; let boatId: BoatId; let car: Car; let boat: Boat; car = getCarById(carId);
除了最后四行外,其他所有内容看起来都不错。 要创建标识符,您需要一个辅助函数:
function makeCarIdFromVin(id: number): CarId { return vin as any; }
这种方法的缺点是该函数将保留在运行时中。
使强类型输入不那么严格
在最后一个示例中,我不得不使用其他功能来创建标识符。 您可以使用Flavor接口定义来摆脱它:
interface Flavoring<FlavorT> { _type?: FlavorT; } export type Flavor<T, FlavorT> = T & Flavoring<FlavorT>;
现在,您可以按以下方式设置标识符的类型:
type CarId = Flavor<number, “CarId”> type BoatId = Flavor<number, “BoatId”>
由于_type属性是可选的,因此可以使用隐式转换:
let boatId: BoatId = 5; // OK let carId: CarId = 3; // OK
而且我们仍然不能混淆标识符:
let carId: CarId = boatId; // ERROR
选择哪个选项
两种选择都有权存在。 品牌化具有保护变量免于直接赋值的优势。 如果该变量以某种格式(例如,绝对文件路径,日期或IP地址)存储字符串,这将很有用。 在这种情况下,处理类型转换的帮助程序功能也可以检查和处理输入数据。 在其他情况下,使用Flavor更方便。
资料来源
- 起点stackoverflow.com
- 免费解释文章