【问题标题】:Typescript function object parameter changes return typeTypescript 函数对象参数更改返回类型
【发布时间】:2022-01-05 14:03:38
【问题描述】:

我一直在努力让对象参数影响函数的返回类型。

主要问题

这是一个带有 createArray 函数的示例,向您展示我的意思。

interface Selectable { selectable: boolean; }

interface SelectableReturn { selected: {value:any, index:number} }

interface Distant { distant: boolean; }

interface DistantReturn { distances: number[]; }

// All possible type combinations (all objects we can pass to the function)
type Possibilties = Distant | Selectable;

// Here we create the function definitions
declare function createArray(arr: any[], options?: undefined): any[];
declare function createArray(arr: any[], options?: Selectable): any[] & SelectableReturn;
declare function createArray(arr: any[], options?: Distant): any[] & DistantReturn;
declare function createArray(arr: any[], options?: Selectable & Distant): any[] & SelectableReturn & DistantReturn;

在未来,您可以想象为对象添加新选项。你能看出你会花多少时间来实现这个吗? 如果我有 5 个选项,我将必须有 5² = 25 个函数声明...

我想要的是根据选项类型自动将属性附加到返回的数组。

第二个问题

如您所见,此代码有效,但是当您像这样创建数组时:

const arr = createArray([1,2,3], {selectable: true});

你没有得到智能感知,这是因为“可能性”界面...... 有没有办法检测“选项”类型,推断返回类型并维护智能感知?

Here 是一个指向 Typescript 游乐场的链接。

【问题讨论】:

  • this approach 是否满足您的需求?如果是这样,我可以写一个答案;如果没有,请告诉我缺少的内容,最好是 edit 您的示例,以展示不满意的用例。
  • 你好@jcalz。谢谢回复。我查看了您的代码并从中学到了一些东西。我不知道 Partial typescript 界面是如何工作的。谢谢。虽然我不太确定这就是我要找的东西。也许我没有为我的问题提供足够的细节。我编辑了我的帖子并添加了一个指向我在操场上创建的代码的链接(我无法在评论部分添加它,因为网址太长了)。
  • 嗯,现在您输出的是返回类型的 intersection 而不是 union,并且您还与一些泛型类型相交 @987654328 @ 没有推理站点(当您调用 createArray() 时如何指定 T)?我可能可以很容易地给出交集而不是联合,但是 T 事情很奇怪,似乎与你所问的问题无关。我有点希望,如果您对我的实现有疑问,您会指出需要更改的具体内容,而不是扩大问题范围。不知道如何继续。
  • 也许我的代码问题与 Intellisense 有关?如果是这样this 可能会更好。如果这对你有用,我会写下来,希望你能保存 intersection-instead-of-union 和 uninferrable-generic-T 以备将来的一些问题。告诉我。
  • 我相信我试图让我的代码更清晰,以便人们理解。但是,如果您说第二部分似乎无关紧要,我可能没有这样做(对不起)。我只关注在第一部分作为参数发送的对象。我应该从一开始就指定我的问题。

标签: typescript types typescript-typings


【解决方案1】:

我在这里采用的方法是创建一个单一的generic 调用签名,它使用options 输入参数的类型来生成所需形状的输出类型,这将是intersectionoptions参数中每个key对应的结果类型。

在这里有帮助的一件事是定义一个类型CreateArrayOptions 表示输入选项键到输出返回类型的映射,以便我们可以以编程方式将结果类型确定为选项键的函数:

interface CreateArrayOptions {
  selectable: SelectableReturn;
  distant: DistantReturn;
}

而不是使用 SelectableDistant 类型,我们只需将 CreateArrayOptions 的键映射到 boolean 属性...好吧,实际上只是 true,因为我不想担心甚至支持某人将选项设置为false。理想情况下,您只是不包含该选项,而是将其设置为false...如果您需要支持false,这是可能的,但更复杂。

这意味着createArray() 调用签名将如下所示:

declare function createArray<K extends keyof CreateArrayOptions = never>(
  arr: any[], options?: Record<K, true> 
): any[] & Return<K>;

其中泛型类型参数Koptions 的键的union(如果未传入options,则为never)。而Return&lt;K&gt; 应该是对应的*Return 类型的交集。

计算方法如下:

type Return<K extends keyof CreateArrayOptions> =
  { [P in K]: (x: CreateArrayOptions[P]) => void }[K] extends
  (x: infer I) => void ? I : never

这可能不会产生交叉点。如果你想要一个union,你可以很简单地像CreateArrayOptions[K] 那样做,因为用键的联合索引到一个接口会产生一个值的联合。但是要从键的联合中以编程方式进行交集,需要更多的类型杂耍。当您use infer in a conditional type 处于具有多个推理候选者的逆变 位置时,会自动为您生成交点。一个这样的位置是函数的参数,假设您使用的是the --strictFunctionTypes compiler option(这也记录了我所说的“逆变”,但也可能看起来像here)。

对于传入的键,我们创建一个mapped type,我们将相应的*Return 类型放在逆变位置,然后infer 这些类型的并集,这会产生一个交集。这也是用于turn a union into an intersection 的通用方法。


让我们看看它是否有效:

const regularArray = createArray([]) // any[]
const alsoRegularArray = createArray([], {}) // any[]
const distantReturn = createArray([], { distant: true }) // any[] & DistantReturn
const selectableReturn = createArray([], { selectable: true }) 
  // any[] & SelectableReturn
const distantAndSelectableReturn = 
  createArray([], { selectable: true, distant: true }) 
  // any[] & SelectableReturn & DistantReturn

看起来不错!

而且它很容易扩展;您所要做的就是向CreateArrayOptions 添加更多条目:

interface CreateArrayOptions {
  selectable: SelectableReturn;
  distant: DistantReturn;
  thirdThing: ThirdThingReturn;
}

const somethingElse = createArray([], { thirdThing: true, distant: true });
// const somethingElse: any[] & DistantReturn & ThirdThingReturn
somethingElse.monkeys // boolean
somethingElse.distances // number[]

对于您的最后一个问题,如果 IntelliSense 不适合您,这似乎是 TypeScript 中的错误或设计限制或缺失功能,其中泛型类型阻止生成合理的 IntelliSense 完成列表。 GitHub 中有很多关于 IntelliSense 缺点的问题,但我不知道我是否找到了正是这个问题的问题。就像,microsoft/TypeScript#28662 看起来相关但并不明显相同。无论如何,在microsoft/TypeScript#28470 中,有a comment 显示了一种解决方法,在这里翻译它会产生以下调用签名:

declare function createArray<K extends keyof CreateArrayOptions = never>(
  arr: any[], 
  options?: Record<K, true> & Partial<Record<keyof CreateArrayOptions, true>>
): any[] & Return<K>;

我们希望options 被视为可分配给Partial&lt;Record&lt;keyof CreateArrayOptions, true&gt;&gt;,对于CreateArrayOptions 的原始版本看起来像{selected?: true; distant?: true}the Partial&lt;T&gt; utility type 使所有属性optional),但仍然让我们推断@ 987654374@。那个路口对我们都有好处。

所以现在你应该得到一些 IntelliSense,它至少知道预期的键,它应该看起来像这样:

// createArray([], { })
// ---------------> |
// ◾ distant   (property) distant?: true | undefined
// ◾ selectable (property) selectable?: true | undefined 

或者对于那些可以看到图像的人:

万岁!

Playground link to code

【讨论】:

  • 太棒了!我不知道为什么,但是当我向 createArray 函数添加另一个泛型类型时,输入不起作用。你知道这是为什么吗? Here 是我试验的代码。
  • 我创建了一个新问题,您可以找到here
猜你喜欢
  • 2020-04-27
  • 2021-05-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-23
  • 1970-01-01
  • 2021-11-28
  • 1970-01-01
  • 2021-05-13
相关资源
最近更新 更多