Typescript:如何期望精确的类实例作为函数参数

2024-03-11

Code

考虑以下代码,一个基类,两个子类,以及一个采用一个子类的一个实例的函数。

abstract class AbstractNumberHolder {
    constructor(private readonly value: number) { }
    getValue() { return this.value; }
}

class UserId extends AbstractNumberHolder {
    equals(u: UserId) { return this.getValue() === u.getValue(); }
}

class SubscriptionCost extends AbstractNumberHolder {
    equals(s: SubscriptionCost) { return this.getValue() === s.getValue(); }
}

// ~~~

function printUserId(u: UserId) {
    console.log(u.getValue());
}

// ~~~

const subCost = new SubscriptionCost(50);
const uid = new UserId(1000105);

printUserId(subCost); // DOES NOT ERROR, WHILE IT SHOULD!

(上述代码的 Typescript Playground 链接 http://www.typescriptlang.org/play/index.html#code/C4TwDgpgBAogHgQwMbAJIDt0QE4B4AqAfFALxS4AihAFAJSnHUVQRzAToAmAzlPlAH4ozAFzDaAbgBQoSLEQoCxMvGRpMOJVABkfaVIQAjbsGxqoSADYJuvAILHTagHIBXALaGcACQD2lzhwoAG8pKHCLX3QTbFcUX2xqMGwASwA3BHYobAgETijLECgMy1cIMXQPL2x6YKgAXzCIgHMIYAA1BFKIOhDsttdsdChgAAsU7gA6ErKJBqlGqSsbXgBVbhxUThY2Dh4oBxiXKp9-QOwQpvCIAEdXLu5qVzF1zc5a-uBB4bGJydaOl0yr0SKCoK5-m1Ot06HNGotlrYoABlVzGJCpMDAFJRADCvhMO3YXHsjjMKDcnlOASCoQiLDuD2o3DEqPRmOxeIJwA+OS+QxG4ymAOhwPooLIwqhQJ6knmiwAZq50CgccNkil0MBXtgtk8Xhtde9LvSkFFuP4IJNLL5mk9IYCYbQ5QspEtzcAoNw0fjCWQsAB3FFo7gYlJYtW+4DUACsAAZJO7op7XCltv6IEGdXqAIxx-N5mOJqQarXZzjMn3cxNAA)

解释

我有两个完全不同的班级UserId and SubscriptionCost它们代表了两个完全不同的概念。由于它们的内部实现暂时相似(都持有一个数值),所以我让它们扩展一个基数AbstractNumberHolder class.


Problem

问题是,打字稿仅比较结构 https://www.typescriptlang.org/docs/handbook/type-compatibility.html#classes类,并且这两个类具有相同的结构!而且很糟糕...

它完全否定了我最初决定选择 Typescript 的原因。如果开发人员不小心将“订阅成本”而不是“用户 ID”传递给函数,并且打字稿无法捕获它,那么我们为什么要费心拥有这些类型呢?


现有的次优解决方案

我环顾四周,思考可能的解决方案。以下是可行的解决方案,但我对其中任何一个都不满意。

解决方案1)手动检查使用instanceof

感谢上帝,instanceof按预期工作!所以,我可以得到我的第一行printUserId be:


function printUserId(u: UserId) {
    assert(u instanceof UserId, "Please pass a UserId!");
    console.log(u.getValue());
}

显然它根本不可扩展,并且对于每个函数的每个参数,我都需要编写这样的一行!

解决方案2)使用Exact在函数上输入

我发现有许多创造性的(手工制作的和非标准的)方法可以让你的类型完全符合你的要求。 (评论这个 github 问题 https://github.com/microsoft/TypeScript/issues/12936, 例如。)

使用它,我可以强制该函数采用exactly一个用户 ID。

type ExactInner<T> = <D>() => (D extends T ? D : D);
type Exact<T> = ExactInner<T> & T;

function printUserId(u: Exact<UserId>) {
    console.log(u.getValue());
}

它比第一个解决方案短得多,但我仍然需要记住把这个Exact<...>在我的代码中每个函数的每个类型参数上!

解决方案 3)向类添加细微差别

先前解决方案的一个主要挫折是,我需要更改功能使用这些类的实例。而且一般来说,代码中的函数比类多。

因此,尝试从不同的角度解决这个问题,我可以向类添加细微差别,使它们不可互换!


class UserId extends AbstractNumberHolder {
    private I_AM_A_USER_ID = true;
}

class SubscriptionCost extends AbstractNumberHolder {
    private I_AM_A_SUBSCRIPTION_COST = true;
}

同样,该解决方案需要对每个类进行手动更改,并且看起来多余。 (它也不能很好地扩展直接来自类工厂的类。)

解决方案 4) 添加私人细微差别extend时间(调味/品牌)

我在以下位置找到了这篇精彩的文章https://michalzalecki.com/nominal-typing-in-typescript/ https://michalzalecki.com/nominal-typing-in-typescript/谈论我们的名义打字问题。还,https://spin.atomicobject.com/2018/01/15/typescript-flexible-nominal-typing/ https://spin.atomicobject.com/2018/01/15/typescript-flexible-nominal-typing/对两种微妙的品牌推广方法进行了很好的比较。

根据那里的解决方案,我可以考虑将字符串(作为假类型)传递给父类,并从中产生细微差别/品牌/风味。

abstract class AbstractNumberHolder<T extends string> {
    private _?: T;
    constructor(private readonly value: number) { }
    getValue() { return this.value; }
}

class UserId extends AbstractNumberHolder<"UserId"> {
    equals(u: UserId) { return this.getValue() === u.getValue(); }
}

class SubscriptionCost extends AbstractNumberHolder<"SubscriptionCost"> {
    equals(s: SubscriptionCost) { return this.getValue() === s.getValue(); }
}

我喜欢这个解决方案的一点是,当我添加更多的类/函数时,我需要做的改变是最小的。另外,您不会错过所需的更改(如T extends string不是可选的)。

然而,这里仍然有一些手工工作。我希望它可以自动化...


那么,对于这个问题,任何人都有更好的解决方案,保证没有开发人员会意外错过(在将来创建更多的类/函数时)并且不需要每个类/函数进行手动工作吗?


None

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Typescript:如何期望精确的类实例作为函数参数 的相关文章

随机推荐

  • git aws.push:没有名为 boto 的模块

    我正在尝试按照教程进行操作 在 AWS Elastic Beanstalk 上部署 Django http docs aws amazon com elasticbeanstalk latest dg create deploy Pytho
  • Pandas:修改特定级别的多索引

    我有一个带有多重索引的数据框 想修改多重索引的一个特定级别 例如 第一级可能是字符串 我可能想从该索引级中删除空格 df index levels 1 x replace for x in df index levels 1 然而 上面的代
  • FastAPI 的部分更新

    我想在 FastAPI 中实现支持部分更新的 put 或 patch 请求 官方文档 https fastapi tiangolo com tutorial body updates 真的很混乱 我不知道如何执行该请求 我不知道items位
  • 从 C# 中的 WebClient 读取响应标头

    我正在尝试创建我的第一个 Windows 客户端 这是我第一次发布她的文章 它将与 Web 服务 进行通信 但是我在读取返回的响应标头时遇到了一些麻烦 在我的响应字符串中 我收到了一个不错的 JSON 文档 这是我的下一个问题 但我无法 查
  • 我们如何在android中以编程方式隐藏抽屉/启动器/应用程序管理器中的图标

    任何人都可以帮我解决 我们如何通过任何其他实用应用程序在手机上随处隐藏或取消隐藏应用程序图标 尝试这个 PackageManager p getPackageManager p setComponentEnabledSetting getC
  • 返回表中列名的 Select 语句是什么

    是否有任何 select 语句可以返回表中的列列表 INFORMATION SCHEMA COLUMNS 视图将提供特定表名的列名 SELECT Column Name FROM INFORMATION SCHEMA COLUMNS WHE
  • Android:在应用程序中间时从 3G 切换到 WIFI = 网络连接丢失

    我在使用 HTC Legend Android 2 2 时遇到了一个恼人的问题 在 Xperia Galaxy Nexus 等上没有看到此问题 当我在 3G 连接上启动应用程序 获取一些数据 然后进入手机设置并启用 WIFI 时 手机会自动
  • 网络音频 API 均衡器

    我一直在寻找使用 Web 音频 API 创建音频均衡器的方法 http webaudio github io web audio api http webaudio github io web audio api 我发现了很多关于创建可视化
  • 使用 StAXResult 调用 Transformer 时省略 XML 声明

    我想将多个 XML 节点从源 XML 文件复制到目标文件 源文件和目标文件都非常大 所以我将使用 StAX 通常 我尝试处理的文件如下所示
  • n 阶 B 树中可以容纳多少个元素?

    是2n吗 只是检查 术语 The Order文献中对 B 树的定义并不一致 例如参见维基百科有关 B 树的文章的术语部分 http en wikipedia org wiki Btree Terminology 一些作者认为它是minimu
  • 禁用右键单击后如何检查 chrome 中的元素?

    我想调试一个信息框 当我将鼠标悬停在谷歌地图标记上时显示该信息框 但谷歌地图禁用右键单击地图画布上的任何位置 因此我无法检查该元素以进行调试 查看目的 我尝试通过元素选项卡中的 href 内容搜索元素 但它没有显示在搜索中 尽管缺少右键单击
  • Oracle“创建表为”空值

    如果您使用 create as 创建一个 Oracle 表 其中某个字段为空 您将收到错误 ORA 01723 不允许零长度列 查询示例 create table mytable as select field a null brand n
  • 架构 i386 的重复符号[关闭]

    这个问题不太可能对任何未来的访客有帮助 它只与一个较小的地理区域 一个特定的时间点或一个非常狭窄的情况相关 通常不适用于全世界的互联网受众 为了帮助使这个问题更广泛地适用 访问帮助中心 help reopen questions 我正在使用
  • jQuery 背景位置动画

    我创建了一个图像 它基本上是由 3 个图像组成的 CSS 精灵 它的大小是 278x123 所以它们基本上是 3 个 278x41 的图像 我想做的是通过改变背景位置来制作动画 我尝试了很多方法 我的一个不太有效的解决方案如下 var sl
  • Java/Mysql..SQLException:表必须至少有 1 列 SQLState:42000 VendorError:1113

    我有一个连接到数据库的应用程序 如果名称尚不存在 则创建一个表 并添加新的注册用户信息 以便我可以将密码散列到我的登录框架 并将它们与数据库进行匹配也将被散列 目前我有以下错误代码 SQLException A table must hav
  • 用类隐藏

    我确信这真的很简单 但这对我来说不起作用 我编写了一个表单 允许用户使用标签从列表中选择一个月
  • Python/WebApp Google App Engine - 测试标头中的用户/通行证

    当您像这样调用网络服务时 username test12 password test34 client httplib2 Http cache client add credentials username password URL htt
  • 检查 UTF-8 字符串在 Qt 中是否有效

    在 Qt 中 有没有办法检查字节数组是否是有效的 UTF 8 序列 看起来QString fromUtf8 http qt project org doc qt 5 0 qtcore qstring html fromUtf8默默地抑制或替
  • jQuery UI 自动完成中的自定义属性问题

    我在使用 jQuery UI 的自动完成功能时遇到了自定义属性的问题 由于某些奇怪的原因 自动完成功能不允许我使用 make 或 id 属性ui item make or ui item id 但在设置为时工作ui item label H
  • Typescript:如何期望精确的类实例作为函数参数

    Code 考虑以下代码 一个基类 两个子类 以及一个采用一个子类的一个实例的函数 abstract class AbstractNumberHolder constructor private readonly value number g