繁体   English   中英

命名泛型函数的返回类型

[英]Naming the return type of a generic function

为了不成为XY问题的受害者,这是我要完成的工作:我有一个通用装饰器创建器函数,该函数返回一个通用装饰器函数(或ES6 mixin)(例如<T1, T2>(...args) => <T3>(base) => class extends base { ... };我想根据通用参数来命名结果的类型,这样我就可以限制其他通用参数(在其他函数中)被传递通过该装饰器至少一次。

我热切地等待着TS 2.8,以为ReturnType可以解决我的问题,但在这种情况下我无法正确使用它。 ReturnType<ReturnType<typeof myMixin>>地,我只是尝试使用ReturnType<ReturnType<typeof myMixin>> ,但是这不起作用,因为myMixin所有类型参数都被强制为{}

下面的(简单)示例说明了该问题:

type ClassConstructor<T> = new(...args: any[]) => T;

const mixin = <B extends ClassConstructor<HTMLElement>, T>(base: B, arg: T) => {
    return class extends base {
        prop: T = arg;
    };
};

const Test1 = mixin(HTMLElement, 10); // using inference
type Test1Prop = typeof (new Test1()).prop; // number

type MixinReturnType = ReturnType<typeof mixin>;
const Test2: MixinReturnType = mixin(HTMLElement, 10); // using explicit type annotation
type Test2Prop = typeof (new Test2()).prop; // {}

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

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

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()所有输出,但又要窄到足以保留mixin()的每个特定输出的确切类型。 没有这样的类型:

const Test2: MixinReturnType = mixin(HTMLElement, 10); 
const t2Prop = new Test2().prop;

const Test3: MixinReturnType = mixin(HTMLElement, "hey");
const t3Prop = new Test3().prop;

你想Test3是同一类型Test2但你想t3Prop从不同类型的t2Prop ,这是行不通的。

* 控制流分析有时会将变量缩小为字符串/数字/布尔文字或并集的单个组成部分,但这不适用于此处。


这可能有效(取决于您要执行的操作)。 使用一般的辅助函数,该函数仅将输入传递到输出,但要求输入可分配给MixinReturnType ,如下所示:

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

注意,该函数在M是通用的; 因此,如果M小于MixinReturnType ,则输出也将更窄。 让我们来看看它的作用:

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

很好,因为现在已知t2Propnumber 您可能想知道这比隐式键入Test1有什么好处。 好吧,这不是真的。 毕竟,编写requireMixinReturnType(mixin(...))并没有多大意义。 但是,现在您确实有一种方法可以防止某人在不扩大输入范围的情况下向您传递错误的混合类型:

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

因此,除非您试图尽早发现错误类型,否则可能不会使用requireMixinReturnType()


您更有可能采用您关心的当前接受MixinReturnType并将其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是任何东西。 现在,编译器意识到pT类型。 让我们尝试一下返回一些东西的东西,例如:

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!

这样可行! 这是非常漫长的。 希望这是有道理的,对我们有所帮助。 祝好运!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM