允许多个不同形状的接口作为 TypeScript 返回类型

2024-01-01

我有一个函数,它接受一些参数并生成将传递到外部进程的对象。由于我无法控制最终需要创建的形状,因此我必须能够为我的函数采用一些不同的参数并将它们组装成适当的对象。这是一个非常基本的示例,展示了我遇到的问题:

interface T1A {
    type: 'type1';
    index: number;
}

interface T1B {
    type: 'type1';
    name: string;
}

interface T2A {
    type: 'type2';
    index: number;
}

interface T2B {
    type: 'type2';
    name: number;
}

function hello(type: 'type1' | 'type2'): T1A | T1B | T2A | T2B {
    return {type, index: 3}
}

这个特定的函数抱怨“type1”不能分配给“type2”。我读了一些有关类型保护的内容,并弄清楚了如何使这个简单的示例变得快乐:

function hello(type: 'type1' | 'type2'): T1A | T1B | T2A | T2B {
    if (type === 'type1') {
        return {type, index: 3}
    } else {
        return {type, index: 3}
    }
}

但是,我不明白为什么这是必要的。类型检查对返回值的可能性完全没有任何作用。基于我的参数仅采用两个显式字符串之一的事实,单个 return 语句保证返回 T1A 或 T2A 之一。在我的实际用例中,我有更多类型和参数,但我分配它们的方式总是保证返回至少一个指定的接口。在处理接口时,联合似乎并不是真正的“这个或这个”。我试图分解我的代码来处理每种单独的类型,但是当有 8 种不同的可能性时,我最终会得到很多额外的 if/else 块,这些块看起来毫无用处。

我也尝试过使用类型type T1A = {...};

我是否误解了工会的某些内容,或者这是一个错误,或者是更简单的处理方法?


一般来说,TypeScript 不会执行从属性向上传播联合的类型。也就是说,虽然该类型的每个值{foo: string | number}应该可以分配给类型{foo: string} | {foo: number},编译器不会将它们视为可相互分配的:

declare let unionProp: { foo: string | number };
const unionTop: { foo: string } | { foo: number } = unionProp; // error!

除了当您开始改变此类类型的属性时发生的奇怪事情之外,对于编译器来说,始终这样做的工作量太大,尤其是当您有多个联合属性时。 Amicrosoft/TypeScript#12052 中的相关评论 https://github.com/microsoft/TypeScript/issues/12052#issuecomment-258653766 says:

这种等价仅适用于具有单一属性的类型,并且在一般情况下并不成立。例如,考虑{ x: "foo" | "bar", y: string | number }相当于{ x: "foo", y: string } | { x: "bar", y: number }因为第一种形式允许所有四种组合,而第二种形式只允许两种特定的组合。

因此,这本身并不是一个错误,而是一个限制:编译器只会花费大量时间来处理联合来查看某些代码是否有效。


在 TypeScript 3.5 中,添加了支持 http://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-5.html#smarter-union-type-checking专门针对以下情况进行上述计算受歧视的工会 http://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions。如果您的联合具有可用于区分联合成员的属性(这意味着单例类型,例如字符串文字 http://www.typescriptlang.org/docs/handbook/advanced-types.html#string-literal-types, 数字文字 http://www.typescriptlang.org/docs/handbook/advanced-types.html#numeric-literal-types, undefined, or null) in 至少有一些工会成员 http://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-2.html#non-unit-types-as-union-discriminants,然后编译器将按照您想要的方式验证您的代码。

这就是为什么如果你改变它就会突然起作用T1A | T2A | T1B | T2B只是T1A | T2A,并且可以是您问题的可能答案:

function hello(type: 'type1' | 'type2'): T1A | T2A | T1B | T2B {
    const x: T1A | T2A = { type, index: 3 }; // okay
    return x;
}

后者是一个受歧视的工会:type属性会告诉您您拥有哪个成员。但前者不是:type属性可以区分T1A | T1B and T2A | T2B,但没有属性可以进一步细分。不,仅仅因为缺席index or name类型中的值不算作判别式,因为类型的值T1A may have a name财产; TypeScript 中的类型不是exact https://github.com/microsoft/TypeScript/issues/12936.

上面的代码之所以有效,是因为编译器可以验证x属于类型T1A | T2A,然后可以验证T1A | T2A是完整的子类型T1A | T2A | T1B | T2B。因此,如果您对两步流程感到满意,那么这对您来说是一个可能的解决方案。


如果你想T1A | T2A | T1B | T2B要成为一个可区分的联合,您需要修改组成类型的定义,以便通过至少一个公共属性来真正区分。就像这样说:

interface T1A {
    type: 'type1';
    index: number;
    name?: never;
}

interface T1B {
    type: 'type1';
    name: string;
    index?: never;
}

interface T2A {
    type: 'type2';
    index: number;
    name?: never;
}

interface T2B {
    type: 'type2';
    name: number;
    index?: never;
}

通过添加类型的那些可选属性never,您现在可以使用type, name, and index作为判别式。这name and index属性有时会是undefined,支持区分类型的单例类型。然后这有效:

function hello(type: 'type1' | 'type2'): T1A | T2A | T1B | T2B {
    return { type, index: 3 }; // okay
}

所以这是两个选择。在您知道某些东西是安全的但编译器不知道的情况下,始终可用的另一个选项是使用类型断言 https://www.typescriptlang.org/docs/handbook/basic-types.html#type-assertions:

function hello2(type: 'type1' | 'type2') {
    return { type, index: 3 } as T1A | T2A | T1B | T2B
}

好的,希望有帮助;祝你好运!

Playground 代码链接 http://www.typescriptlang.org/play/#code/CYUwxgNghgTiAEEQBd4FcB2BLA9hgCjDgA4Bc8A3vAGY47kDOyMWGA5vAD7wZoC2AIxAx4AXwDcAKDB4m6bHgAqJclVr14TFuzFdKNOuV6DhugLzzcBIsXHwA9PfjCiMAIST4kjFD4gGxFBgCAAiWAxgLHyUnvBxrMjC1EEIigCMAIIxcTk5yACexCDkAOQFRWklUrm5rKAAHkb8QjDVOaKSsfEYiTDJwfDpAELZNXHlxfBlhSCVbWM+fozMrGzzYp21PUkpgwBMWRRdNROlE3tVx1sNTSatxx3HCTsDinsjR2PjM2czF+u5RaTYwtdaPXLUTBgZBWeAACxAEAgOAAFKcphNKnppkULgBKcjpLLcN7EwZpEYk96jMYyDByRrksmk+AWKgTAA08DqIEZAGYxACcnBkGgYBh4PUwZscpCMNDYQikTg9mifhiZljuDiQPiaTURWKJeyZlyefzdFAGEy9CySRTbe8Hp1HkCArsALJQADWIDCESi+uefV2RP1uXROrmVxy5tuoJjcSBAH4jCAAG7CaVPbYh14Oz5fSOYy5fHi+SZaVZC7oNVM8DNZ5053r9VIHcN5dU6-6J7kYG48ZpNssptOZ+65cGx3Nt-YfPvFv6lr5A+Mjr7m+sYRuT9oyuJyhV4eGI5Fqoq-ColbHnEoEm1U5kOqkLsuG8X6Tn9wcCiTNx4gA

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

允许多个不同形状的接口作为 TypeScript 返回类型 的相关文章

随机推荐

  • 指定 Tkinter 文本框的尺寸(以像素为单位)

    如何使用像素指定 Tkinter 文本框的尺寸 我不想改变字体大小 我用它来帮助我将其缩放到窗口的大小 您可以通过将其放入框架内 通过停用尺寸传播并配置条目以粘贴到框架边框来强制框架为固定尺寸来实现此目的 pack 也应该以类似的方式工作
  • 屏幕上和变量中的调用表达式输出

    我正在尝试存储来自的输出Invoke expression到变量以及屏幕上 我有 PS 日志记录 它会自动将所有内容记录为Write Host在一个文件中 现在我正在使用Invoke Expression这似乎要么在屏幕上打印输出 要么打印
  • 对 pandas MultiIndex 数据帧进行重新采样

    我有一个类似于以下内容的 pandas MultiIndex 数据框 import pandas as pd rows One One One 20120105 1 Text1 One One One 20120107 2 Text2 On
  • swift ios 键盘扩展 - 长按/按住

    我需要知道如何向 ios 自定义键盘扩展添加长按 按住 功能 以便我可以显示多个键以从中选择一个 expected design 我的项目结构 按键操作的当前代码 代码可以将按下的按钮的标题作为新文本添加到任何代理文本字段中 IBActio
  • Xcode PhoneGap 本地化

    这两天我一直在努力尝试向我的应用程序添加特定于语言的本地化 该应用程序正在使用 PhoneGap 1 1 我正在尝试使用 XCode 4 1 构建它 两天以来我一直在寻找信息 教程 其他内容 但找不到任何详细信息 所以 要么这是一个微不足道
  • XStream解析没有根节点的JSON

    我目前正在使用 XStream 反序列化 JSON 并且效果很好 但是 当我有如下所示的 JSON 字符串时 key1 an object something foobar key2 another object data hi 最值得注意
  • 在哪里放置模型数据和行为? [tl;博士;使用服务]

    我正在使用 AngularJS 来完成我的最新项目 在文档和教程中 所有模型数据都放入控制器范围内 我知道它必须可供控制器使用 因此必须在相应的视图中 但我认为该模型实际上不应该在那里实施 例如 它可能很复杂并且具有私有属性 此外 人们可能
  • 将文本框绑定到comboBox.SelectedItem 的属性

    我正在使用 winforms 并且有一个代表 IQueryable 的组合框 组合框下方是一系列文本框 我希望将它们绑定到组合框中当前选择的文本框 这是我的代码 public partial class TestForm Form publ
  • 如何访问 keras 中单个自定义损失函数的所有输出

    我正在尝试重现中提出的网络架构本出版物 https www sciencedirect com science article pii S0031320319302031在张量流中 作为一个完全的初学者 我一直在使用本教程 https ww
  • 如何在使用 Component Creator 构建的 Joomla 3.x 组件的一个视图中包含多个模型

    Joomla 组件使用 MVC 模型 Component Creator 是一种广泛使用的工具 其付费级别支持通过 SQL 导入创建多表视图 此外 开发人员根据 Joomla 文档从头开始构建组件 我想构建一个高级组件 充当 仪表板 显示来
  • Python:从字符串中提取多个浮点数

    请原谅我 我是 Python 新手 给定一个以不确定长度的浮点数开头并以相同长度结尾的字符串 我如何将它们都提取到一个数组中 或者如果只有一个浮点数 则只有一个 例子 38 00 SALE 15 20 69 99 我想返回 38 00 15
  • 计算 AP 的距离,包括信噪比

    出于某种原因 我和一个朋友正在讨论根据从设备获取的信息 RSSI 频率 SNR 等 计算您自己 笔记本电脑 手机等 与 AP 之间的距离 因此 在花了一些时间研究三边测量 三角测量和自由空间路径损耗之后 在一些博客文章和 wiki 的帮助下
  • bootstrap-affix :affix 下面的 Div“跳”到顶部。如何让它平滑地向后滚动?

    已经使用 bootstrap 几天了 并对它提供的功能感到惊讶 一直在尝试拥有某种 标题 当用户向下滚动时将其固定在顶部 您可以在这里找到我当前的工作 http mp3dj free fr affix site http mp3dj fre
  • 比较 SynchronizationContext

    如何比较 SynchronizationContext 看来同一个Dispatcher在使用BeginInvoke时可以创建不同的SynchronizationContext 当我深入研究两个 不相等的 上下文时 我发现调度程序线程 ID
  • 在记事本++中创建新快捷方式

    我正在尝试在记事本 中添加快捷方式或按钮来调用我当前正在编辑的文件上的外部程序 例如 假设我有程序 analyzer jar 我想在记事本 中创建一个按钮 或快捷方式 来直接运行命令 cmd K java jar analyzer jar
  • 在 AWS EC2 上添加新的 Spark 工作线程 - 访问错误

    我有现有的正在运行的 Spark 集群 它是通过以下方式启动的spark ec2脚本 我正在尝试按照说明添加新的从站 停止集群 在 AWS 控制台上 在其中一个从属设备上 启动更多这样的操作 启动集群 尽管新实例已添加到同一安全组 并且我可
  • Y 轴中带有长标签的 Recharts 水平条形图的动态高度

    如果 YAxis 中的标签太长 如何动态更改 Recharts 中的 ResponsiveContainer 的高度 这是我的代码
  • 错误:打开模拟器时,在 Windows 8 上找不到抛出的模拟器图像 (avds)

    我正在 ionic 框架中制作一个 cordova 应用程序 我想在模拟器 somfired 命令中测试它ionic emulate android它引发了以下错误 运行命令 ionic emulate android 时 Git Bash
  • ControlSend 使用 SetKeyDelay, 0, 0 时随机发送错误字符(修改和未修改)

    我正在自我回答这个问题 因为我在互联网上看到过这个问题 但几乎没有有用的答案 而且在 Stack Overflow 上也绝对找不到我能找到的解决方案 示例代码 考虑一下这段代码 它只写了几行 shell 命令 0 SetKeyDelay 0
  • 允许多个不同形状的接口作为 TypeScript 返回类型

    我有一个函数 它接受一些参数并生成将传递到外部进程的对象 由于我无法控制最终需要创建的形状 因此我必须能够为我的函数采用一些不同的参数并将它们组装成适当的对象 这是一个非常基本的示例 展示了我遇到的问题 interface T1A type