TypeScript 中多个互斥参数

2024-04-30

给定以下 JavaScript 函数:

function x({foo, fooId, bar, barId}) {}

我想将其转换为 TypeScript,以便调用者必须传入foo or fooId,但不能两者兼而有之。同样对于bar and barId.

例如,x({foo: "", bar: ""}) and x({fooId: "", bar: ""})允许函数调用,但编译器会阻止x({foo: "", fooId: "", bar: ""}) and x({bar: ""}).

这对于 TypeScript 类型系统是否可行以及如何实现?


您要寻找的类型是这样的:

type XArgs = 
 { foo: any; fooId?: never; bar: any; barId?: never; } |
 { foo: any; fooId?: never; barId: any; bar?: never; } | 
 { fooId: any; foo?: never; bar: any; barId?: never; } | 
 { fooId: any; foo?: never; barId: any; bar?: never; };
      
function x({ foo, fooId, bar, barId }: XArgs) { }

x({ foo: "", bar: "" }); // okay
x({ fooId: "", bar: "" }); // okay
x({ foo: "", fooId: "", bar: "" }); // error
x({ bar: "" }); // error

So XArgs is a union https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types有四种可能性。我们看第一个:

{ foo: any; fooId?: never; bar: any; barId?: never }

So foo and bar是类型的属性any,所以它们必须存在。但fooId and barId are 可选属性 https://www.typescriptlang.org/docs/handbook/2/objects.html#optional-properties(表示为?) of 不可能的事never type https://www.typescriptlang.org/docs/handbook/2/narrowing.html#the-never-type。没有以下值never类型,所以你实际上不能提供一个定义的fooId or barId那里的属性...并且由于可以省略可选属性,因此 type 的可选属性never本质上是被禁止的。所以这个类型意味着foo and bar是必需的,并且fooId and barId被禁止。

其他三个工会成员类似,只是接受和禁止的属性不同。四个工会成员一起XArgs描述允许的参数的全部范围x().

这就是所问问题的答案。


但是手动写出必要的联合可能会非常乏味,特别是如果你的联合中有两个以上的成员的话。排他性工会 https://en.wikipedia.org/wiki/Exclusive_or(您希望只出现一个元素),或者您关心的两组以上属性。

如果是这样,你可以让编译器计算XArgs如下:

type AllKeys<T> = T extends unknown ? keyof T : never
type ExclusiveUnion<T, K extends PropertyKey = AllKeys<T>> = 
  T extends unknown ? (T & { [P in Exclude<K, keyof T>]?: never }) : never;

The AllKeys<T>类型是一个分配条件类型 https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types计算并集的钥匙 https://www.typescriptlang.org/docs/handbook/2/keyof-types.html#the-keyof-type-operator每个工会成员T, so AllKeys<{a: 0} | {b: 1}> is "a" | "b".

Then ExclusiveUnion<T>type 是另一种分布式条件类型,它采用像这样的联合{a: string} | {b: number} | {c: boolean}并产生一个排他版本,其中每个成员明确禁止仅出现在其他成员中的成员。 (它用AllKeys获取其他成员的钥匙。)这相当于{a: string, b?: never, c?: never} | {a?: never, b: number, c?: never} | {a?: never, b?: never, c: boolean}.

注意我说的是“等同”;你实际上得到了工会交叉点 https://www.typescriptlang.org/docs/handbook/2/objects.html#intersection-types, like {a: string} & {b?: never, c?: never},这可能会变得笨拙。

所以我有一个Expand<T> 递归条件类型 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html#recursive-conditional-types在展开任何其他别名属性时折叠交集:

type Expand<T> = T extends object ? { [K in keyof T]: Expand<T[K]> } : T;

然后我们定义XArgs作为一个交集ExclusiveUnions, and Expand让它变得漂亮:

type XArgs = Expand<
  ExclusiveUnion<{ foo: any } | { fooId: any }> &
  ExclusiveUnion<{ bar: any } | { barId: any }>
>;

这正是

type XArgs = 
 { foo: any; fooId?: never; bar: any; barId?: never; } |
 { foo: any; fooId?: never; barId: any; bar?: never; } | 
 { fooId: any; foo?: never; bar: any; barId?: never; } | 
 { fooId: any; foo?: never; barId: any; bar?: never; };

让我们尝试一下更难手写的类型:

type YArgs = Expand<
  ExclusiveUnion<{ a: 0 } | { b: 1 } | { c: 2 }> &
  ExclusiveUnion<{ x: 9 } | { y: 8 } | { z: 7 }>
>
/* type YArgs = 
  { a: 0, b?: never, c?: never, x: 9, y?: never, z?: never; } | 
  { a: 0, b?: never, c?: never, y: 8, x?: never, z?: never; } | 
  { a: 0, b?: never, c?: never, z: 7, x?: never, y?: never; } | 
  { b: 1, a?: never, c?: never, x: 9, y?: never, z?: never; } | 
  { b: 1, a?: never, c?: never, y: 8, x?: never, z?: never; } | 
  { b: 1, a?: never, c?: never, z: 7, x?: never, y?: never; } | 
  { c: 2, a?: never, b?: never, x: 9, y?: never, z?: never; } | 
  { c: 2, a?: never, b?: never, y: 8, x?: never, z?: never; } | 
  { c: 2, a?: never, b?: never, z: 7, x?: never, y?: never; } */

看起来不错!

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

TypeScript 中多个互斥参数 的相关文章

随机推荐

  • 由于配置错误,执行失败:Java 中的 Lambda 代理响应格式错误

    我的函数是通过代理 API 网关调用的 该函数执行正常 但 API 调用返回 502 错误 Mon Dec 30 18 16 25 UTC 2019 Endpoint response body before transformations
  • 在 Windows 上安装 Apache Spark

    我需要安装阿帕奇火花 http spark apache org 在 Windows 机器上 根据文档 我应该在我的机器上安装 sbt 并覆盖其默认选项以使用最大 2G RAM 经过多次尝试后 我决定选择 maven 我覆盖了默认选项以使用
  • 检查类型是否为未知类型

    有没有办法检查类型参数T事实上是unknown type 我知道可以检查any 解决方案在这里 https stackoverflow com a 55541672 11153160 但想知道unknown 最简单的解决方案是这样的 typ
  • CRTP:表达式模板的编译器相关问题

    我遇到了以下代码 存储在 crtp cc 中 的编译器相关问题 include
  • 是否可以在 SignalR hub 方法中返回预编码的 JSON 字符串?

    在 MVC 项目中 我在集线器上有一个与此类似的方法 public string Foo return DoCrazyThingThatReturnsJson 不幸的是 SignalR 或其他东西 接受编码的 JSON 字符串并愉快地对其进
  • 使某些代码只运行一次

    我有一些代码只想在 MainViewController 中运行一次 它应该在每次用户启动应用程序时运行 但仅在 MainViewController 加载后运行 我不想运行它 void applicationDidFinishLaunch
  • 在 Google App Engine 上运行的 Spring Boot Web 应用程序 - 引发 jetty 异常

    当我执行时gradlew appengineRun 我一直得到 Starting a Gradle Daemon subsequent builds will be faster Mar 26 2018 5 47 48 PM java ut
  • 来自 alpine 软件包存储库的 Numpy 无法导入 c 扩展

    我正在制作一个需要 pandas 和 numpy 的 docker 映像 但通过 pip 安装大约需要 20 分钟 这对于我的用例来说太长了 然后我选择从 alpine 软件包存储库安装 pandas 和 numpy 但似乎无法正确导入 n
  • Java日期格式转换

    我目前正在从请求对象中提取三个表单字段输入 日 月 年 该月的第 11 天为 Day 11 12 月为 12 月 今年将是 2010 年 我需要将其转换为 Java Date 对象 但由于发生了很多变化 我不确定将其存储在 java 对象中
  • Code::Blocks 中的单个项目中有多个主要 C++ 文件?

    我不经常写代码 有时我连续 6 个月每天编写代码 然后长达 2 年不编写代码 这种方法迫使我保留一堆我 以及其他更好的程序员 编写的代码或参考代码 时隔很长一段时间 我在写代码时都会提到这个 库 我读了它 我执行了它 这对我精神焕发有很大帮
  • 查找 system.out.println 代码中的位置

    假设我正在一个非常大的项目中工作 并且注意到有一个空的打印行 所以我假设有一个 System out println 位于代码中的某个位置 除了在整个项目中搜索所有出现的 System out println 之外 我该如何尝试找出它在哪里
  • 在 C++ 中类继承的情况下强制延迟方法解析

    考虑以下类结构 class foo public int fun cout lt lt in foo lt lt endl class bar class1 public foo public int fun cout lt lt in b
  • 编译 XNA 项目时,无法执行请求的操作错误

    第二次编译项目时 出现以下错误消息 我必须关闭 VS 2010 重新加载时它会编译 如果我做出改变 那么问题又回来了 无法复制文件 obj x86 Debug MyFile dll 无法对打开的用户映射部分的文件执行请求的操作 我因为另一个
  • Swift NSPredicate 不在

    我有一个包含 X 个项目的数组 我需要通过 uid 过滤掉特定项目 我写了以下谓词 我认为它是正确的 我面临的问题是 Swift 编译器只允许我使用接受 argumentArray 的初始化程序 let uids 34885a9f0897f
  • 首选的跨平台 IPC Perl 模块是什么?

    我想创建一个简单的 IO 对象 它代表一个向另一个程序打开的管道 我可以在应用程序运行时定期写入另一个程序的 STDIN 我希望它是防弹的 因为它可以捕获所有错误 并且是跨平台的 我能找到的最佳选择是 open sub io read lo
  • 如何避免 Selenium 中的“StaleElementReferenceException”?

    我正在使用 Java 实现大量 Selenium 测试 有时 我的测试由于以下原因失败StaleElementReferenceException https developer mozilla org en US docs Web Web
  • App Store 上是否允许嵌入 dylib 的 iOS 8 应用程序?

    iOS 8 现在支持动态框架 是否意味着 App Store 提交允许这样做 似乎以前的开发人员能够在内部应用程序中使用 dylib 但在提交到 App Store 的内容中使用它们会导致您被拒绝 情况仍然如此 还是 iOS 8 中的这一更
  • 如何在快速人工智能中获得给定测试集的预测并计算准确性?

    我正在尝试加载由导出的学习者learn export 我想针对测试集运行它 我希望我的测试集有标签 以便我可以测量其准确性 这是我的代码 test src TextList from df df path cols texts split
  • 重新审视混合字符串值的字母数字排序

    请注意 我之前提出了一个非常相似的问题 但要求已发生变化 对混合字符串值进行字母数字排序 https stackoverflow com questions 3842719 alphanumeric sort on mixed string
  • TypeScript 中多个互斥参数

    给定以下 JavaScript 函数 function x foo fooId bar barId 我想将其转换为 TypeScript 以便调用者必须传入foo or fooId 但不能两者兼而有之 同样对于bar and barId 例