命名泛型函数的返回类型

时间:2018-04-11 21:16:01

标签: typescript generics decorator es6-class

为了不成为XY问题的牺牲品,这就是我想要完成的事情:我有一个通用装饰器创建器函数,它返回一个通用装饰器函数(或ES6 mixin)(如import pyspark.sql.functions as f df.union( df.groupBy("State")\ .agg(f.sum("Count").alias("Count"))\ .select("State", f.lit("Total").alias("City"), "Count") ).sort("State", "City", "Count").show() #+-----+---------+-----+ #|State| City|Count| #+-----+---------+-----+ #| MA| Boston| 11| #| MA|Cambridge| 3| #| MA| Quincy| 5| #| MA| Total| 19| #| WA| Seattle| 10| #| WA| Tacoma| 11| #| WA| Total| 21| #+-----+---------+-----+ 和我想要根据通用参数命名结果的类型。 这样我就可以将其他通用参数(在其他函数中)限制为至少一次通过该装饰器。

我急切地等待TS 2.8并认为<T1, T2>(...args) => <T3>(base) => class extends base { ... };会解决我的问题,但我无法在这种情况下正确使用它。天真的我只是尝试使用ReturnType,但这并不起作用,因为ReturnType<ReturnType<typeof myMixin>>的所有类型参数都被强制转换为myMixin

以下(更简单)示例说明了问题:

{}

TypeScript推断正确地命名实例属性的类型。然而,只要我给它起一个名字,就会变成垃圾。我有什么可以做的吗?

1 个答案:

答案 0 :(得分:0)

这是我对正在发生的事情的看法。如果明确注释类型比其值更宽的变量类型,则编译器(通常为*)会忘记该值的较窄类型,并仅将该变量视为较宽类型。例如:

interface General {
  foo: string;
}
interface Specific extends General {
  bar: number
}
declare function getSpecific(): Specific;
declare function acceptSpecific(x: Specific): void;

const implicitlyTyped = getSpecific();
acceptSpecific(implicitlyTyped); // okay

const explicitlyTyped: General = getSpecific();
acceptSpecific(explicitlyTyped); // error!

explicitlyTyped变量仅被编译器理解为General类型,因此调用acceptSpecific(explicitlyTyped)是错误的。这是因为展开MixinReturnType会导致Test2的所有后续检查让您失望而导致的问题。

因此,解决方案无法找到MixinReturnType的版本,该版本以某种方式通用/宽度足以接受mixin()的所有输出,但又足够窄以保留{{1}的每个特定输出的确切类型1}}。不存在这样的类型:

mixin()

您希望const Test2: MixinReturnType = mixin(HTMLElement, 10); const t2Prop = new Test2().prop; const Test3: MixinReturnType = mixin(HTMLElement, "hey"); const t3Prop = new Test3().prop; Test3的类型相同,但您希望Test2t3Prop的类型不同,而t2Prop只是不起作用。

* Control flow analysis有时会将变量缩小为字符串/数字/布尔文字或联合的单个成分,但这不适用于此处。

这里有一些可行的东西(取决于你正在尝试做什么)。使用只是将输入传递到输出的通用辅助函数,但要求输入可以分配给MixinReturnType,如下所示:

const requireMixinReturnType = <M extends MixinReturnType>(m: M) => m;

请注意,此功能在M中是通用的;因此,如果MMixinReturnType窄,则输出也会更窄。让我们看看它的实际效果:

const Test2 = requireMixinReturnType(mixin(HTMLElement, 10));         
const t2Prop = new Test2().prop; // number

这很好,因为t2Prop现在已知为number。您可能想知道这比隐式键入的Test1更好。嗯,这不是真的;毕竟,写requireMixinReturnType(mixin(...))并没有多大意义。但是现在你确实有办法防止有人在没有扩大输入的情况下向你传递一个糟糕的mixin类型:

const BadMixin = requireMixinReturnType(HTMLElement); // error!
// Property 'prop' is missing in type 'HTMLElement'.

所以你可能不会使用requireMixinReturnType(),除非你提前尝试抓住一个糟糕的类型。

更有可能的是,您会接受您关心的功能,该功能目前接受MixinReturnType并使其成为通用的:

declare function doConcrete(m: MixinReturnType): void {
   const p = new m().prop; // {}, oops
}

declare function doGeneric<M extends MixinReturnType>(m: M): void {
       const p = new m().prop; // still {}, oops
}

哦,哦,TypeScript不够聪明,不能使用通用M并提取prop的类型。有一些方法可以解决这个问题。最直接的是忘记MixinReturnType而是考虑我们关心的通用定义。像这样:

function doGeneric<T, E extends HTMLElement>(
  m: ClassConstructor<E & { prop: T }>
): void {
  const p = new m().prop; // T, that's good
}

在这种情况下,我们说m参数必须是返回E & {prop: T}类型的构造函数,其中E是可分配给HTMLElement的内容}和T是什么。现在,编译器意识到p的类型为T。让我们尝试一些可以返回的内容,例如:

function getProp<T, E extends HTMLElement>(
  m: ClassConstructor<E & { prop: T }>
): T {
  return new m().prop; // type checks, yay!
}

const t2Prop = getProp(Test2); // number, yay!

有效!这是非常啰嗦的;希望它有意义并且有所帮助。祝你好运!