【问题标题】:Typescript method return type based on optional param property function基于可选参数属性函数的打字稿方法返回类型
【发布时间】:2021-10-26 15:32:31
【问题描述】:

尝试创建一个简单的实用程序,它可以:

  • 按原样返回给定数组
  • 或根据给定的可选参数进行转换。 这是代码:
type MapperFn<T, U> =  (val: T) => U;

interface mapperOpts<T,U> {
  cb?: MapperFn<T,U>
}

interface mapper {
  map<T, U, Z extends mapperOpts<T,U>>(arr: Array<T>, opts: Z): Z extends { cb: MapperFn<T,U> } ? U[]: T[];
}

const obj: mapper = {
  map: (arr, { cb }) => {
    if (!cb) return arr;
    return arr.map(cb);
  }
}

const arr: number[] =[1,2,3];

const result = obj.map(arr, {cb: (element) => element.toString() }); // should be typed as `string[]`

const result2 = obj.map(arr, { cb: (element) => element+1 }); // should be typed as `number[]`

const result3 = obj.map(arr, {}); // should be types as `number[]`

但是,我收到了错误:

Type '<T, U, Z extends mapperOpts<T, U>>(arr: T[], { cb }: Z) => T[] | U[]' is not assignable to type '<T, U, Z extends mapperOpts<T, U>>(arr: T[], opts: Z) => Z extends { cb: MapperFn<T, U>; } ? U[] : T[]'.
  Type 'T[] | U[]' is not assignable to type 'Z extends { cb: MapperFn<T, U>; } ? U[] : T[]'.
    Type 'T[]' is not assignable to type 'Z extends { cb: MapperFn<T, U>; } ? U[] : T[]'.

请注意,result 和 result2 被标记为 unknown[],这可能意味着来自回调函数的参数类型推断无法正常工作。

我错过了什么?

Typescript Playground

【问题讨论】:

  • U 作为单独的类型参数没有合理的推理站点;你最好把它排除在外并从Z推断它,比如this。这能满足你的需求吗? (我不明白你的一些用例,其中result2 将是number[],尽管在各个方面都与result1 相同,或者为什么你期望number 而不是number[] 而不是result3。哈!如果这些是错误,请修复它们,这样你就有一个真正的minimal reproducible example)。如果是这样,我会写一个答案;如果没有,请详细说明什么不适合您。
  • 对不起!我粘贴了错误的链接。我已经更正了源代码和打字稿游乐场链接。我看到了你的链接,这似乎是我想要的!我最初是从这个开始的:tsplay.dev/WPjqqN 工作正常,并认为我可以从那里继续前进。您能否进一步解释一下没有合理的 U 推断站点作为单独的类型参数,或者指出一个来源以便我更好地理解为什么会发生这种情况?

标签: typescript type-inference optional-parameters conditional-types


【解决方案1】:

generic 类型参数推断的工作方式的细节似乎没有特别好的文档记录,除了 now obsolete TypeScript Language Specification。

但一般情况下,当编译器看到调用签名为(比如)func&lt;T, U, V&gt;(x: F&lt;T, U&gt;, y: G&lt;T, V&gt;): H&lt;U, V&gt;; 形式为c = func(a, b) 的函数时,它需要尝试推断类型参数T、U , 和 V 来自值 a、b 和 c 的类型。为了推断U,编译器需要检查a 和c 的类型,因为参数x 和返回类型都依赖于U。所以x 和返回类型是T 的潜在推理站点。另一方面,尝试使用b 来推断U 的任何东西是没有希望的,因为y 参数的类型根本不引用U。也就是说,y 不是U 的推理站点。

并非所有推理站点都受到平等对待,有些推理站点比其他推理站点更难推理。返回类型通常是较差的推理站点,因为编译器通常不知道预期的返回类型......如果你写了const c = func(a, b);,你要求编译器推断c的类型,所以func()的返回类型不知道。您只能在 c 已经是 const c: SomeType = func(a, b); 等已知类型的情况下使用返回类型。

涉及类型参数的类型函数越复杂,推理站点的用处就越小。对于f&lt;T&gt;(x: T): void 之类的东西,对f(a) 的调用将很容易将T 推断为a 的类型。但是对于像g&lt;T&gt;(x: T[keyof T]): void 这样的东西,几乎不可能从g(a) 推断出T。在前一种情况下,您从相同类型的值推断T。简单的。在后一种情况下,从该类型属性的联合值推断T。不明显你会如何开始。其他类型的函数往往介于这些极端之间。如果遇到问题,最好的办法是简化推理站点中的类型函数。

最后,有些地方根本不用作推理站点。像h&lt;T, U extends T&gt;(u: U): void 这样的函数签名没有T 的推理站点。推断类型参数时不参考Generic constraints。也许您可能希望编译器从传递给h() 的内容中推断出U,然后从U 中推断出T,但事实并非如此。 T 肯定不会被推断出来,会退回到 unknown 之类的东西。

对于出现这种情况的问题,您可以查看microsoft/TypeScript#38183、microsoft/TypeScript#31529,可能还有许多其他问题(我会搜索“推理站点”)。


所以,说了这么多,我对你的map() 方法的建议是这样的:

interface Mapper {
  map<T, Z extends MapperOpts<T, any>>(arr: Array<T>, opts: Z):
    Z extends { cb: MapperFn<T, infer U> } ? U[] : T[];
}

在之前的版本中,U 没有合理的推理站点。相反,我们将仅从 arr 和 opts 推断出 T 和 Z。这很可能会成功。由此,我们可以使用Z 通过显式conditional type inference 提取U。

让我们看看它是如何工作的:

const result = obj.map(arr, { cb: (element) => element.toString() }); // string[]
const result2 = obj.map(arr, { cb: (element) => element + 1 }); // number[]
const result3 = obj.map(arr, {}); // number[]

看起来不错!

Playground link to code

【讨论】:

  • 感谢您如此详细的回答!我印象深刻!
  • 有一件事要问。在代码map&lt;T, U, Z extends mapperOpts&lt;T,U&gt;&gt;(arr: Array&lt;T&gt;, opts: Z): Z extends { cb: MapperFn&lt;T,U&gt; } ? U[]: T[]; 中U 的使用在mapperOpts&lt;T,U&gt; 中不会被视为推理站点吗?
  • 否,因为“在推断类型参数时不参考通用约束”(请参阅​​以该文本开头的段落)。 mapperOpts&lt;T, U&gt; 仅出现在Z extends mapperOpts&lt;T, U&gt; 的约束位置。您可能希望 opts 让 Z 被推断出来,然后 Z 让 U 被推断出来,但它不起作用。
  • 知道了...谢谢!
  • 那是github.com/microsoft/TypeScript/issues/33912,这是 TS 中的一个未解决问题。编译器几乎不可能验证任何特定值是否属于依赖于未解析的泛型类型参数的条件类型。请注意,如果您需要从某人那里获得更多时间,您可能应该发布一个新问题(在搜索现有问题之后),而不是依赖已回答问题的评论部分。祝你好运!
猜你喜欢
  • 2021-05-16
  • 2021-01-23
  • 1970-01-01
  • 2019-11-15
  • 2022-01-12
  • 2015-08-10
  • 1970-01-01
  • 2017-06-01
  • 1970-01-01
相关资源
最近更新 更多