首页 > 解决方案 > 在通过构造函数进行泛型初始化期间类型未正确断言

问题描述

我在 TypeScript 中遇到了一个小问题,我不确定是我输入错误,还是 TypeScript 无法正确断言类型。让我展示重现问题所需的所有代码:


interface IRawFoo { type: string };

type FooConstructor = new (...args: any[]) => BaseFoo;

class BaseFoo { }
class Ext1Foo extends BaseFoo { }
class Ext2Foo extends BaseFoo { }

const FooConstructorMap = {
    "ext1": Ext1Foo,
    "ext2": Ext2Foo,
}

function getConstructor(rawFoo: IRawFoo): FooConstructor {
    return FooConstructorMap[rawFoo.type];
}

function getInstance(rawFoo: IRawFoo): BaseFoo {
    const fooConstructor = getConstructor(rawFoo);
    return initializeFoo(rawFoo, fooConstructor);
}

function initializeFoo<T extends FooConstructor>(rawFoo: IRawFoo, constructor: T): InstanceType<T> {
    const newFoo = new constructor();

    /* Doing stuff for each FOO instance. */

    return newFoo; // TS ERROR HERE
}

/* USAGE EXAMPLE .*/
const ext1RawFoo : IRawFoo = { type: "ext2" };
const ext1Foo = initializeFoo(ext1RawFoo, Ext1Foo); // <- In this example, ext1Foo is a "Ext1Foo".

现在,让我试着快速解释一下:

当前代码在以下return语句中给了我这个错误initializeFoo
Type 'BaseFoo' is not assignable to type 'InstanceType<T>'.ts(2322)

不过,我不确定为什么,因为我写的代码对我来说很有意义。在initializeFoo中,我断言Textends FooConstructor,因此T是一个具有构造函数的对象,该构造函数返回BaseFoo. 然后,我还说initializeFoo返回一个InstanceType<T>. 由于 instanciatedT返回 a BaseFoodue to T extends FooConstructor,我认为InstanceType<T> extends BaseFoo可以做出断言。但是,TypeScript 没有。

在这一点上,我不知道我是否写错了,是否有更好的方法来写这个,或者 TypeScript 不够聪明,无法做出这样的假设。

我知道我可以这样写签名initializeFoo
function initializeFoo<T extends FooConstructor>(rawFoo: IRawFoo, constructor: T): BaseFoo

但是,当我写这个时:
const ext1Foo = initializeFoo(ext1RawFoo, Ext1Foo);

ext1Foo类型将是BaseFoo而不是Ext1Foo.. 我不喜欢。

最后,我知道我可以通过以下方式解决这个问题:
return newFoo as InstanceType<T>

但是使用as,在我看来,由于编写了错误的代码,我在作弊。

谢谢!

标签: javascripttypescripttypescript-generics

解决方案


所以这里棘手的部分是拥有的含义T extends FooConstructor。由于FooConstructor被定义为new (...args: any[]) => BaseFoo,扩展FooConstructor的东西也是一个返回BaseFoo;的函数。它只是可能需要更具体的参数,具有更多属性等。您想要的是“返回扩展类型BaseFoo的构造函数”,而不是“扩展返回构造函数类型的类型的构造函数BaseFoo”。

以下是如何重新定义构造函数类型并初始化函数以按预期工作:

type FooConstructor<T extends BaseFoo = BaseFoo> = new (...args: any[]) => T;

// ...

function initializeFoo<T extends BaseFoo>(rawFoo: IRawFoo, constructor: FooConstructor<T>): T {
    const newFoo = new constructor();

    /* Doing stuff for each FOO instance. */

    return newFoo;
}

推荐阅读