【问题标题】:Conditional mapped typed with unions make functions uncallable使用联合类型的条件映射使函数不可调用
【发布时间】:2018-03-22 21:57:15
【问题描述】:
type Container<T> = T extends any[] ? ArrayContainer<T> : ObjectContainer<T>

type ArrayContainer<T> = {
  set(arr: T, index: number): void
}

type ObjectContainer<T> = {
  set(obj: T): void
}

const testContainer = {} as any as Container<{ boxedNumber: number } | number>

// Does not compile
testContainer.set(33)

条件类型决定返回一个ObjectContainer&lt;{ boxedNumber: number }&gt; | ObjectContainer&lt;number&gt;,使得调用 ObjectContainer 上的任何函数都是不可能的。

有没有办法强制返回ObjectContainer&lt;{ boxedNumber: number } | number&gt;

使用可用的最新 2.8 版本。

【问题讨论】:

    标签: typescript


    【解决方案1】:

    正如@TitianCernicovaDragomir 所指出的,您的问题是T extends XXX ? YYY : ZZZ 中带有裸类型参数T 的条件类型最终分布在T 中的联合上。这是as designed,但有一个de-facto standard workaround:将TXXX“装箱”成一个单元组,如[T] extends [XXX] ? YYY : ZZZ。这使得条件不依赖于裸类型参数,并且不再破坏联合。 (T 最终处于协变位置的任何东西都应该工作......{a: T} extends {a: XXX} 也可以工作)。这很可能会继续工作并得到支持,因为 @ahejlsberg 是推荐它的人,而他是 TypeScript 中的 main architect of conditional types

    所以在你的情况下你会这样做

    type Container<T> = [T] extends [any[]] ? ArrayContainer<T> : ObjectContainer<T>
    

    它会根据需要将Container&lt;string | number&gt; 解释为ObjectContainer&lt;string | number&gt;

    但请注意,现在Container&lt;string | number[]&gt; 被解释为ObjectContainer&lt;string | number[]&gt;,因为string | number[] 本身并不是一个数组。可以吗?应该是ObjectContainer&lt;string&gt; | ArrayContainer&lt;number[]&gt;?还是ObjectContainer&lt;string&gt; &amp; ArrayContainer&lt;number[]&gt;?或者是其他东西?没有把握。

    希望有所帮助;祝你好运。

    【讨论】:

    • 你没有忘记实际的解决方案type Container&lt;T&gt; = [T] extends [any[]] ? ArrayContainer&lt;T&gt; : ObjectContainer&lt;T&gt; 吗?
    • 我很好,只有纯数组进入 ArrayContainers,任何联合进入 ObjectContainer
    【解决方案2】:

    问题是条件类型会遍历联合中的类型并将条件应用于联合中的每个项目并联合结果,因此Container&lt;{ boxedNumber: number } | number&gt; 的结果将等同于ObjectContainer&lt;number&gt; | ObjectContainer&lt;{ boxedNumber: number; }&gt; 而不是ObjectContainer&lt;number | { boxedNumber: number; }&gt; .

    您可以创建 Container&lt;{ boxedNumber: number }&gt; 和 Container 的交集类型,其行为类似于您对 Container&lt;number | { boxedNumber: number }&gt; 的期望

    const testContainer = {} as any as Container<{ boxedNumber: number }> & Container<number>
    testContainer.set(33)
    testContainer.set({ boxedNumber: 19})
    

    【讨论】:

    • 如果 OP 想要阻止条件分布在联合上,事实上的标准方法是将条件“装箱”成一个像 type Container&lt;T&gt; = [T] extends [any[]] ? ArrayContainer&lt;T&gt; : ObjectContainer&lt;T&gt; 这样的单元组。
    • @jcalz 我不知道 :) 10x。你会添加答案吗,这是你的:)?
    • @jcalz 哇哦,没想到会这样。无论如何它都有效!这在未来打破的可能性有多大?如果您发布答案,很高兴批准这一点,是的。
    • 呃,好吧,让我找到文档,我会发布答案。
    • 我通常使用T[number &amp; keyof T] 之类的东西来解决这个问题。或者你可以做丑陋的[T] extends [any[]] ? (T extends any[] ? T[number] : never) : ...
    【解决方案3】:

    看来你需要额外的类型参数。

    type Container<T, R> = T extends any[] ? ArrayContainer<R> : ObjectContainer<R>;
    
    type ArrayContainer<T> = {
      set(arr: T, index: number): void
    }
    
    type ObjectContainer<T> = {
      set(obj: T): void
    }
    
    const testContainer = {} as Container<{}, number>;
    testContainer.set(10);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-04-06
      • 2019-01-18
      • 2019-01-27
      • 1970-01-01
      • 1970-01-01
      • 2021-07-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多