【问题标题】:Does Typescript support mutually exclusive types?Typescript 是否支持互斥类型?
【发布时间】:2017-06-26 16:16:24
【问题描述】:

我有一个带参数的方法。我希望 Typescript 验证传入的对象(在 typescript 编译时,我理解运行时是另一种动物)只满足一个允许的接口。

例子:

interface Person {ethnicity: string;}
interface Pet {breed: string;}
function getOrigin(value: Person ^ Pet){...}

getOrigin({}); //Error
getOrigin({ethnicity: 'abc'}); //OK
getOrigin({breed: 'def'}); //OK
getOrigin({ethnicity: 'abc', breed: 'def'});//Error

我意识到 Person ^ Pet 不是有效的 Typescript,但这是我想尝试的第一件事,而且看起来很合理。

【问题讨论】:

  • 认为您正在寻找的是Type Guards。 AFAIK TypeScript 不支持独占类型。
  • 对于希望在界面的所有键中只保留一个属性的人,我建议使用oneOf

标签: typescript


【解决方案1】:

按照this issue 中的建议,您可以使用conditional types 编写异或类型:

type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
type XOR<T, U> = (T | U) extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;

现在你的例子有效了:

interface Person {ethnicity: string;}
interface Pet {breed: string;}
function getOrigin(value: XOR<Person, Pet>) { /* ... */}

getOrigin({}); //Error
getOrigin({ethnicity: 'abc'}); //OK
getOrigin({breed: 'def'}); //OK
getOrigin({ethnicity: 'abc', breed: 'def'});//Error

【讨论】:

  • XOR 太方便了!如何扩展它以支持多种互斥类型?您能否扩展您的答案以支持XOR&lt;Person, Pet, Car, Tree, ..., House&gt;
  • 需要建立一个相应的链(我怀疑它的工作原理是因为传递性?!):type XOR3&lt;S, T, U&gt; = XOR&lt;S, XOR&lt;T, U&gt;&gt;;
  • @maninak 参加聚会有点晚了,但this(TS 游乐场)会用OneOf&lt;[Person, Pet, Car, Tree, ..., House]&gt; 完成您所寻找的工作
  • 可怕的@tjjfvi... :)
  • @tjjfvi 你写的吗?当我使用它时,我希望能够归功于创作者:)
【解决方案2】:

您可以使用专门用于解决此问题的小型 npm 包 ts-xor

使用它,您可以执行以下操作:

import { XOR } from 'ts-xor'

interface A {
  a: string
}

interface B {
  b: string
}

let A_XOR_B: XOR<A, B>

A_XOR_B = { a: 'a' }          // OK
A_XOR_B = { b: 'b' }          // OK
A_XOR_B = { a: 'a', b: 'b' }  // fails
A_XOR_B = {}                  // fails

完全披露:我是这个小包的作者。我发现我需要一直实现从 repo-to-repo 的 XOR 类型。所以我为我和社区发布了它,这样我也可以使用自述文件和共享的 jsdoc 注释正确记录它。该实现是@Guilherme Agostinelli 从社区分享的内容。

【讨论】:

    【解决方案3】:

    为了补充 Nitzan 的答案,如果您真的想要强制指定 ethnicitybreed 相互排他,您可以使用映射类型来强制缺少某些字段:

    type Not<T> = {
        [P in keyof T]?: void;
    };
    interface Person {ethnicity: string;}
    interface Pet {breed: string;}
    function getOrigin(value: Person & Not<Pet>): void;
    function getOrigin(value: Pet & Not<Person>): void;
    function getOrigin(value: Person | Pet) { }
    
    getOrigin({}); //Error
    getOrigin({ethnicity: 'abc'}); //OK
    getOrigin({breed: 'def'}); //OK
    
    var both = {ethnicity: 'abc', breed: 'def'};
    getOrigin(both);//Error
    

    【讨论】:

      【解决方案4】:

      你可以使用Discriminating Unions:

      interface Person {
        readonly discriminator: "Person"
        ethnicity: string
      }
      
      interface Pet {
        readonly discriminator: "Pet"
        breed: string
      }
      
      function getOrigin(value: Person | Pet) { }
      
      getOrigin({ }) // Error
      getOrigin({ discriminator: "Person", ethnicity: "abc" }) // OK
      getOrigin({ discriminator: "Pet", breed: "def"}) // OK
      getOrigin({ discriminator: "Person", ethnicity: "abc", breed: "def"}) // Error
      

      【讨论】:

        【解决方案5】:

        你可以使用union types:

        function getOrigin(value: Person | Pet) { }
        

        但最后一条语句不会出错:

        getOrigin({ethnicity: 'abc', breed: 'def'}); // fine!
        

        如果您希望这是一个错误,那么您需要使用 overloading:

        function getOrigin(value: Pet);
        function getOrigin(value: Person);
        function getOrigin(value: Person | Pet) {}
        

        【讨论】:

        • 联合类型不能解决问题,有点像 OP 要求用什么来替换它,但函数重载非常适合这个用例。
        猜你喜欢
        • 2016-08-20
        • 1970-01-01
        • 2011-06-13
        • 2018-09-07
        • 2012-10-04
        • 1970-01-01
        • 2023-03-30
        • 2019-04-24
        • 1970-01-01
        相关资源
        最近更新 更多