【问题标题】:Infer type T using type assertion of other type which is conditioned on T使用以 T 为条件的其他类型的类型断言来推断类型 T
【发布时间】:2021-10-07 13:53:13
【问题描述】:

考虑这个检索“项目”或“项目”数组的函数:

function foo<T extends Item | Item[]>(id: T extends Item ? string : undefined): T {
  ...
}

这样做的目的是,如果您想要单个项目,则使用 id 调用 foo,如果您想要一组项目,则使用 id 而不使用 id。这很有效,因此调用者可以这样做:

foo&lt;MyItem&gt;('42')

foo&lt;MyItem[]&gt;(undefined)

(可以使用诸如function foo&lt;T extends Item | Item[]&gt;(...[id]: T extends Item ? [string] : []): T { 之类的可变参数来更好地实现这一点,以便在检索数组时启用不带参数调用foo

但是,在foo 的实现中,我注意到如果我检查id 的类型:

if (id) { // if typeof id === 'string' gives same result
  ...
}

...然后在那个 if 子句中,编译器不理解类型 T 现在必须扩展 Item 并且 not Item[] (由于 id 是一个字符串仅当 T extends Item 时。如果我想调用任何依赖于 T 为 Item 的代码,我仍然需要执行 T as Item,否则我会得到:

TS2344: Type 'T' does not satisfy the constraint 'Item'.   Type 'Item | Item[]' is not assignable to type 'Item'.     Property 'id' is missing in type 'Item[]' but required in type 'Item'.

换句话说,id 的类型以T 的类型为条件,但 T 的类型不会通过断言 id 是一个字符串来推断/缩小。 有可能以某种方式解决这个问题吗?这是一个简单的as 演员阵容,但我只是好奇。

【问题讨论】:

  • 有关信息,请参阅 the GitHub issues linked here。您不能通过检查id 来缩小T,因为T 可能是完整的Item | Item[] 联合。你想说“T extends Item OR T extends Item[]”而不是T extends Item | Item[],但是 TS 中没有表达这一点的工具。如果您不担心这种可能性,您应该只使用类型断言来抑制错误。
  • 我认为最好避免异类联合。但这只是我的意见

标签: typescript typescript-generics


【解决方案1】:

本质上,您希望函数根据传递的参数返回不同的内容。

这是一份更适合overloads的工作。

type Item = {};

function foo<T extends Item>(): T[];
function foo<T extends Item>(id: string): T;
function foo<T extends Item>(id?: string): T | T[] {
  const item = {} as T; // for demo purpose only

  if (id){
    return item;
  }

  return [item];
}

const test1 = foo();      // Item[]
const test2 = foo('1');   // Item

检查playground here

作为一般经验法则,如果您的输入逻辑涉及函数实现,请使用重载。在定义不处理运行时逻辑的类型或接口时,Conditional types 通常更有用。

【讨论】:

    【解决方案2】:

    您可以使返回类型以参数类型为条件,而不是相反:

    function foo<T extends string | undefined>(id: T): T extends string ? Item : Item[] {
      // ...
    }
    

    或者如果你只想用绝对是字符串或绝对未定义的东西来调用它,你可以使用重载而不是让它泛型:这样你就可以在没有参数的情况下调用函数,而不是将undefined 作为参数传递。

    function foo(): Item[];
    function foo(id: string): Item;
    function foo(id?: string): Item | Item[] {
      // ...
    }
    

    这些解决方案之间的实际区别在于,使用重载时,您的函数不能使用 string | undefined 类型的参数调用,因此在编译时始终知道特定调用将解析为单个项目还是项目数组。

    请注意,这两种解决方案都避免了使用 Item &amp; HTMLCanvasElement 之类的类型参数调用您的函数然后返回类型不正确的问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-12-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多