【问题标题】:Generics - Use return type of a parameter function as return type泛型 - 使用参数函数的返回类型作为返回类型
【发布时间】:2020-07-23 01:47:15
【问题描述】:

我有一个执行获取请求的奇特函数。第一个参数描述了请求。第二个参数是一个可选的转换函数:

let data = useData({ /* desc request */}, (res) => res.values)

转换函数在函数返回之前转换响应(即设置data)。

这些函数的签名是:

// Signature for the transform function
type TransformFn<Data, R> = (data: Data) => R;

// Signature for the fetcher
function useData<Data = any>(
  firstArg: object,
): Data;
function useData<Data = any, R = any>(
  firstArg: object,
  fn?: TransformFn<Data, R>
): R;

如果我这样做,效果会很好:

type Response = { values: string[] }

let data = useData<Response, Response["values"]>({ /* desc request */ }, (res) => res.values);

但是如果我不必传入第二个类型参数 (TransformedData),那就太棒了。我希望能够键入转换函数的参数,然后让 TypeScript 的惊人推理从那里获取它,如下所示:

let data = useData<Response>({ /* desc request */ }, (res) => res.value);

就目前而言,data 只是回退到默认类型 arg,any

有什么提示吗?谢谢。

【问题讨论】:

  • 去掉= any不就行了吗?您甚至不需要显式指定泛型类型参数。 typescriptlang.org/play/#code/…
  • 嘿@zerkms - 感谢您的回复。您给出的示例存在问题:提供给useData 的第一个参数是Data 类型的notuseData 在幕后使用该参数来发出获取请求。 那个获取请求返回一个响应(Data),可以选择通过转换函数传递

标签: typescript


【解决方案1】:

不幸的是,TypeScript 不支持部分类型参数推断(请参阅microsoft/TypeScript#26242 进行讨论)。现在要么全有,要么全无:要么让编译器推断所有类型参数,要么指定所有类型参数。 (看起来generic parameter defaults 可以为您做到这一点,因为它只允许您指定一些参数;但未指定的参数不是推断,而是采用默认 价值。这不是你想要的。)

除非 microsoft/TypeScript#26242 以某种方式实现,否则只有解决方法。我通常使用的两种解决方法是:


咖喱

由于您不能同时指定和推断同一个泛型函数的类型参数,因此您可以将该单个泛型函数拆分为多个函数,其中一个您指定参数,另一个让编译器为您推断它们。例如:

declare function useDataCurry<D>():
    <R = D>(firstArg: object, fn?: TransformFn<D, R>) => R;

这里的useDataCurry() 是一个没有参数的函数,它返回另一个函数。 D(你的Data)类型参数可以在useDataCurry()上指定,R类型参数可以在返回的第二个函数上推断。你可以这样使用它:

let data0 = useDataCurry<Response>()({ /*desc*/ }); // Response
let data1 = useDataCurry<Response>()({ /*desc*/ }, (res) => res.values); // string[]

这几乎正是您想要的,除了有一个看起来很奇怪的中间函数调用。


假装

另一种方法是让编译器推断所有类型参数,但您传入一个与您要指定的类型相对应的类型的虚拟参数。它甚至不必是类型的真实值;您可以使用type assertion 使null 看起来像您想要的类型。

例如:

declare function useDataDummy<D, R = D>(
    dummyD: D, firstArg: object, fn?: TransformFn<D, R>): R;

dummyD 参数将在函数的实现中被忽略,但它让编译器知道你想要 D 的类型:

let data2 = useDataDummy(null! as Response, { /*desc*/ }); // Response
let data3 = useDataDummy(null! as Response, { /*desc*/ }, res => res.values); // string[]

这也几乎正是您想要的,只是您将类型参数指定为就好像它是常规参数一样。这很奇怪,但它确实有效。


任何一种方式都有效,但都不是完美的。那好吧。无论如何,希望有所帮助;祝你好运!

Playground link to code

【讨论】:

  • 这是一个非常棒的回应,感谢@jcalz。刚刚试用,效果很好。不幸的是,这是一个图书馆,所以我有很多想法要做。无论如何,我将来如何改进这个问题的标题以提高谷歌能力?真的不知道该怎么称呼这个难题。
【解决方案2】:

而不是

function useData<Data = any, R = any>(
  firstArg: object,
  fn?: TransformFn<Data, R>
): R;

您可以使用ReturnType utility type 类型为fn,看起来像这样

function useData<Data = any>(
  firstArg: object,
  fn?: TransformFn<Data, unknown>
): ReturnType<typeof fn>;

这会从您的签名中删除第二个泛型类型参数,与您的示例中所需的内容相匹配,并将 useData 的结果键入返回类型 fn

【讨论】:

  • 感谢ReturnType 的指点 - 没听说过!这让我更接近,但看起来通过将TransformFn 的第二个类型参数标记为unknownuseData 的返回类型变为unknown。这不是我想要的——我希望 TS 会为我推断返回类型
  • 是的。当您实际调用 useData 时,结果应该至少具有从第二个参数推断出的函数返回类型的类型。不过我同意,未知类型不是很有帮助。
猜你喜欢
  • 2021-12-13
  • 1970-01-01
  • 1970-01-01
  • 2018-12-24
  • 2020-10-26
  • 2021-05-24
  • 2015-08-18
  • 1970-01-01
  • 2017-07-07
相关资源
最近更新 更多