从枚举参数推断 Typescript 函数返回类型

2024-05-23

我想创建一个加载服务,为枚举中定义的 ID 返回正确类型的数据。我所做的看起来像这样:

enum IdentifierEnum {
  ID1 = 'ID1', 
  ID2 = 'ID2'
}

interface DataType {
  [IdentifierEnum.ID1]: number,
  [IdentifierEnum.ID2]: string
}

class LoadingService {
  loadData<K extends IdentifierEnum>(key: K): DataType[K] {
    // ...
  }
}

使用这种方法,在使用加载服务时可以正确推断类型:

const loadingService: LoadingService = new LoadingService();
const data1 = loadingService.loadData(IdentifierEnum.ID1); // type: number
const data2 = loadingService.loadData(IdentifierEnum.ID2); // type: string

我面临的唯一问题是,在实施内部loadData, 类型参数K仅推断为IdentifierEnum。因此,以下内容将不起作用:

class LoadingService {
  loadData<K extends IdentifierEnum>(key: K): DataType[K] {
    if (key === IdentifierEnum.ID1) {
      return 1; // Error: Type '1' is not assignable to type 'DataType[K]'.
    }
    // ..
  }
}

对我来说,情况确实如此。尽管如此,我还是希望有一个完全类型安全的解决方案。

我已经尝试过重载该函数,但这给我留下了一个问题,即我仍然必须提供一个实现签名,该签名要么太具体(如上面),要么太笼统,这再次消除了我想要的类型安全性。转换返回值也是如此。我基本上需要的是一种真正对输入值进行类型检查的方法,而不仅仅是检查其值。

有可能这样做吗?或者也许有一种完全不同的方法来解决这个问题,为加载服务的使用和实现提供类型安全?

边注

对于这个简单的目的,实现可能看起来过于复杂,但原因是加载服务有一个通用基类,如下所示:

// Base class
abstract class AbstractLoadingService<E extends string | number | symbol, T extends {[K in E]: any}> {
  abstract loadData<K extends E>(key: K): T[K];
}

// Implementation
class LoadingService extends AbstractLoadingService<IdentifierEnum, DataType> {
  loadData<K extends IdentifierEnum>(key: K): DataType[K] {
    // ...
  }
}

这是 TypeScript 中的一个已知痛点,请参阅微软/TypeScript#13995 https://github.com/microsoft/TypeScript/issues/13995 and 微软/TypeScript#24085 https://github.com/microsoft/TypeScript/issues/24085。问题是基于控制流的类型分析 https://github.com/microsoft/TypeScript/pull/8010不适用于泛型类型参数。

If key被声明为类型IdentifierEnum,然后检查if (key === IdentifierEnum.ID1) {...}会缩小类型key在 - 的里面{...}块成为Identifier.ID1:

const k: IdentifierEnum = key;
if (k === IdentifierEnum.ID1) {
  k; // const k: IdentifierEnum.ID1
} else {
  k; // const k: IdentifierEnum.ID2
}
 

这就是控制流分析。现在这不会发生key是泛型类型K,但即使它确实如此,它也不会帮助你:

if (k === IdentifierEnum.ID1) {
  return 1; // ERROR! 
}

这是因为,无论如何,从 TypeScript 3.8 开始,即使value类型的K缩小了,类型K本身就是not。编译器从来不会说“如果key is IdentifierEnum.ID1, then K is IdentifierEnum.ID1因此,如果您希望编译器为您验证类型安全,则不能使用这种基于控制流的实现。

TypeScript 的未来版本可能会以某种方式改善这一点,但这很棘手。一般来说,只是因为一个值x类型的X可以缩小到键入Y,这并不意味着类型X本身可以缩小。如果您有多个类型值,这一点是显而易见的X围坐。但无论如何,就目前而言,这是需要解决的问题。


您已经探索过类型安全性较差的方法,并且对此感到不满意:类型断言和重载签名,因此我将不再写出您将如何实现它。


在这里做一些相对类型安全的事情的唯一方法是放弃控制流分析,而使用索引操作。编译器足够聪明,可以意识到如果你有一个值t类型的T和一个值k类型的K extends keyof T,即该值t[k]将是类型T[K]。就你而言,T is DataType。因此,您需要该类型的值来索引:

class LoadingService {
  loadData<K extends IdentifierEnum>(key: K): DataType[K] {
    return {
      get [IdentifierEnum.ID1]() { return 1; },
      get [IdentifierEnum.ID2]() { return "" }
    }[key]; // okay
  }
}

上述类型检查。请注意,我将属性实现为getters https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get。你不必这样做;你可以直接写:

return {
  [IdentifierEnum.ID1]: 1,
  [IdentifierEnum.ID2]: ""
}[key];

但是 getter 版本允许您进行更任意的计算,并且知道只有对应于key将会得到评价。


Playground 代码链接 https://www.typescriptlang.org/play/#code/KYOwrgtgBAkgJqALgSwGbOAJwKLmgbwCgpYARARigF4oByGC2gGmLICZq6G3bCBfQoWQhEWVAEMAxsCilxicQBUAngAcZREgG14SNBhx4AdA3IBdAFxQ8AIywttukfqy5IJ0m0tQAzokzCAOb8gpIANuI+PlAAMgD24nBBAMpYAG7I0lCaUGEJcHIKADwA0lDAAB6iIHDRTijorngAfAAUANbAylYlAJRWhUpqwFolZtmCJCSScSB+UO1W9S6GkJydygDcrCRoUB3UVDTLjasQHuS9E1NT7ZtQAPQPUDNziAtLCM6nbuemO1A+OUwj4NACSHdHs9XvNFrAvg0DL8PGwAQIAXsDkdjgiVsjTFccjdMMBEGBMCAoOR7k8oNgAEr0gDy9IAhFA0ZMpiSyRTrjcoIFSVAdLifsZTGZWoSoDzyZTqYCHAKhe9RXpxe5uFKZXK+QAifWAtFaDZmbYkATomHvPKJFLpTLAKzxe0gQKpTAZLI0EDAADusXyDq9Tul2xtUDg8nElBodqS7s93uARgTg1aJyREoovRpz0QwystiwhEj0YUHHjwaTjukafyGazTS1njzUKghfUVj8AXdgkIQA

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

从枚举参数推断 Typescript 函数返回类型 的相关文章

随机推荐

  • pywin32:如何从进程句柄获取窗口句柄,反之亦然

    两个用例 枚举窗口 然后获取每个窗口的进程句柄 枚举进程 然后获取每个进程的主应用程序窗口句柄 枚举窗口 然后获取每个窗口的进程句柄 您需要这些 API win32gui EnumWindows http timgolden me uk p
  • 使用 Selenium ChromeDriver 设置 Chrome 的语言

    我下载了ChromeDriver 默认浏览器语言是英语 我需要将其更改为西班牙语 但一直无法 public WebDriver getDriver String locale System setProperty webdriver chr
  • 与自定义键盘扩展进行通信,主机应用程序无法在设备中运行,但可以在模拟器中运行

    我正在使用自定义键盘扩展 这几乎完成了 但当我与扩展和我的主机应用程序通信数据时 我只是面临设备问题 该应用程序在设备中没有工作 但在模拟器中工作也是如此 我的代码如下 HostApp 视图控制器 void viewDidLoad defa
  • JavaScript 索引

    我对 javascript 不太了解 所以我在让以下脚本工作时遇到问题 我需要检查输入的名称是否也包含在消息中
  • 如何在 Eclipse CDT 中查看静态或全局变量?

    我一直试图弄清楚如何在 CDT 的 eclipse 变量窗口中显示静态变量 但不知道如何 CDT 的菜单按钮似乎没有 java gt globals 菜单 如何在 Eclipse CDT 调试器中显示静态 全局变量 Window gt Sh
  • 从 firefox 扩展获取 firefox 选项卡的 url

    在 Firefox 扩展中 如何枚举当前窗口的选项卡并检索它们的 URL 有一个代码片段位于MDC https developer mozilla org en US docs Archive Add ons Tabbed browser
  • 基于值的类混乱

    我正在寻求一些澄清基于值的类的定义 https docs oracle com javase 8 docs api java lang doc files ValueBased html 我无法想象 最后一个要点 6 应该如何与第一个要点一
  • C++ 内部如何存储引用? [复制]

    这个问题在这里已经有答案了 我只是想知道 引用是如何内部存储的 我觉得深入了解该级别将使我更好地理解指针与引用的概念并做出决策选择 我怀疑它的工作原理基本上与指针相同 但编译器负责处理指针 请指教 根本不要求以任何方式 存储 引用 就语言而
  • Microsoft.Bcl.Async 中是否有 ExceptionDispatchInfo 的类似物?

    有没有类似的ExceptionDispatchInfo http msdn microsoft com en us library system runtime exceptionservices exceptiondispatchinfo
  • 如何编写不返回任何内容的 postgres 存储过程?

    如何在 postgres 中编写一个根本不返回值的简单存储过程 即使使用 void 返回类型 当我调用存储过程时 我也会返回一行 CREATE FUNCTION somefunc in id bigint RETURNS void AS B
  • 最佳开源混合整数优化求解器[关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我正在使用 CPLEX 来解决巨大的优化模型 超过 100k 个变量 现在我想看看是否可以找到开源替代
  • 在 iPhone 上下载、保存和播放 mp3

    我想从某个网站下载 mp3 文件 将其保存到我的 CoreData 模型 AudioMp3 中 然后播放 下面的函数可以工作 但首先 效率低下 因为它必须首先将 mp3 保存到文件 其次 它在接下来的调用次数中重复播放相同的 mp3 我认为
  • 定位精度定义 - iOS

    iOS 上返回的 准确性 或 不确定性 的统计意图是什么 即使是近似值 例如 Android 文档对其返回的精度数字进行了解释 从这个意义上讲 它大约是一个标准差 我们将准确度定义为 68 置信度的半径 换句话说 如果 您以该位置的纬度和经
  • SqlConnection - 是远程连接还是本地连接?

    如果我有 SqlConnection 对象 如何确定它是本地连接 localhost 或 127 0 0 1 还是远程连接 本地区域的其他计算机 使用连接询问 SQL 语句 SELECT SERVERNAME 然后验证这是否与客户端计算机的
  • 尽管遵循安装说明,Beaker 仍无法找到 Python 和 Julia 安装

    我最近安装了 Beaker Notebook 但无法启动 Python 我已经安装了 Python 它是使用 Anaconda 安装的 实际上推荐用于 Beaker 我已经编辑过beaker pref json指向我的安装 见下文 但它无法
  • SonarQube 不收集代码覆盖率

    我在使用 sonar runner 和 jacoco 设置 gradle 时遇到问题 除了代码覆盖率之外 一切都很好 我已经尝试了一切但没有结果 这是我的 build gradle 文件 apply from dependencies gr
  • 防止应用程序在控制台关闭时退出

    I use AllocConsole 在 winform 应用程序中打开控制台 如何防止应用程序在控制台关闭时退出 EDIT 不时更新的完成百分比是我想在控制台中显示的 void bkpDBFull PercentComplete obje
  • PHP-MySQLi 连接随机失败并显示“无法分配请求的地址”

    大约两周以来 我一直在处理 LAMP 堆栈中最奇怪的问题之一 长话短说 与 MySQL 服务器的随机连接失败并显示错误消息 Warning mysqli real connect HY000 2002 Cannot assign reque
  • NSIndexpath.item 与 NSIndexpath.row

    有谁知道之间的区别NSIndexpath row and NSIndexpath item 具体来说 我在以下情况中使用哪一个 UITableViewCell tableView UITableView tableView cellForR
  • 从枚举参数推断 Typescript 函数返回类型

    我想创建一个加载服务 为枚举中定义的 ID 返回正确类型的数据 我所做的看起来像这样 enum IdentifierEnum ID1 ID1 ID2 ID2 interface DataType IdentifierEnum ID1 num