typescript - 我如何为扩展简单接口(interface)的通用输出键入一个类?

标签 typescript

我正在定义一个类,它通常将一种对象类型转变为另一种对象类型,它有一个非常简单的接口(interface):id:string, type:string

我希望类定义表明它将返回一个程序集,并通过实例化来指示它将返回哪个程序集。

编译错误(第 79 行 return assembly;):

Type 'Assembly' is not assignable to type 'Out'. 'Assembly' is assignable to the constraint of type 'Out', but 'Out' could be instantiated with a different subtype of constraint 'Assembly'.(2322)

typescript 代码:

interface IAssemblyRequirements {
    id: string;
    type: string;
}

interface IData extends IAssemblyRequirements {
    attributes: any;
}

interface IFooBarData extends IData {
    attributes: {
        start: string;
    }
}

interface IBazBarData extends IData {
    attributes: {
        chickens: number;
    }
}

const foobarData: IFooBarData = {

    id: "1",
    type: "foobar",
    attributes: {
        start: "Dec 1, 2020"
    }

}

const bazbarData:IBazBarData = {

    id: "2",
    type: "bazbar",
    attributes: {
        chickens: 9
    }

}

class Assembly implements IAssemblyRequirements {

    id:string;
    type:string;

    constructor(data: IData) {
        this.id = data.id;
        this.type = data.type;
    }

}

class FooBar extends Assembly {
    start:Date;
    constructor(data: IFooBarData) {
        super(data);
        this.start = new Date(data.attributes.start);
    }
}

class BazBar extends Assembly {
    chickens: number;
    constructor(data: IBazBarData) {
        super(data);
        this.chickens = data.attributes.chickens;
    }
}

const typeAssemblers:{ [key:string]: typeof Assembly } = {
    foobar: FooBar,
    bazbar: BazBar
}

class Assembler<In extends IData, Out extends Assembly> {

    assemble(input: In): Out {
        const assembly = new typeAssemblers[ input.type ]( input );
        return assembly;
    }

}

const assembler = new Assembler<IFooBarData, FooBar>();
const assembly = assembler.assemble(foobarData);

最佳答案

你当然可以用泛型表示输入和输出类型之间的关系,但编译器通常无法验证你的 Assembler.assemble() 的实现。遵守它。那将需要支持我一直称之为 correlated record types 的东西;现在,像 new typeAssemblers[input.type](input) 这样的构造可能需要一些 type assertions .因此,请为此做好准备。


您的类型定义缺少的一件事是 IFooBarDataIBazBarDatatype类型的属性 string ,但您的实现只有在缩小到正确的 "foobar" 时才有效和 "bazbar" string literal types ;否则有人可以制作 IFooBarDatatype类似"oopsie" 的属性并且实现会在运行时爆炸。因此,这里对这些定义进行了一些更改:

interface IFooBarData extends IData {
    attributes: {
        start: string;
    }
    type: "foobar"
}

interface IBazBarData extends IData {
    attributes: {
        chickens: number;
    }
    type: "bazbar"
}

另一个问题是注释 typeAssemblers类型为 { [key:string]: typeof Assembly }丢弃了很多类型信息。编译器不知道 typeAssemblers.foobar持有 FooBartypeAssemblers.oopsie不存在。相反,我建议不要注释 typeAssemblers根本上,为它推断一个更窄的类型。我们也可以为缩小的类型命名:

const typeAssemblers = {
    foobar: FooBar,
    bazbar: BazBar
}
type TypeAssemblers = typeof typeAssemblers;

此类型被编译器推断为 {foobar: typeof FooBar; bazbar: typeof BazBar;} .


最后,Assembler类实际上应该只有一个泛型类型参数。 In之间的关系类型和 Out类型由 TypeAssemblers 设置;不应允许用户询问 In正在IBazBarData同时OutFooBar .所以我们只使用 In类型(我称之为 I ),从中我们可以计算输出类型为 InstanceType<TypeAssemblers[I["type"]]> (这意味着:I 输入有一个 type 属性,可用于索引到 TypeAssemblers ,这将生成一个构造函数,其实例类型是我们将输出的类型)。

这是 Assembler ,使用我之前警告过的那些类型断言:

class Assembler<I extends IFooBarData | IBazBarData> {
    assemble(input: I) {
        return new typeAssemblers[input.type](
            input as any
        ) as InstanceType<TypeAssemblers[I["type"]]>;
    }
}

现在终于可以测试了:

const assembler = new Assembler<IFooBarData>();
const assembly = assembler.assemble(foobarData); // FooBar
console.log(assembly.start.getFullYear()); // 2020

看起来不错,可以编译;编译器知道 assemblyFooBar .


好的,希望对您有所帮助;祝你好运!

Playground link to code

关于typescript - 我如何为扩展简单接口(interface)的通用输出键入一个类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59779249/

相关文章:

angular - location.hash 改变时如何使用@HostListener? ( Angular 2)

javascript - 使用 Angular 2 RC5 路由器,在给定 ActivatedRouteSnapshot 的情况下,我如何导航?

javascript - 检测泛型类型参数是否具有 id 属性?

Typescript CORS 已启用但仍然出现 CORS 错误

typescript - typescript 中的输出和目录结构

Angular 4延迟加载不加载组件

typescript - 如何将可变参数传递给基类

javascript - 如何从在 NestJS CQRS 的传奇中失败的后续命令引发 HTTP 异常?

typescript - 如何键入 React 组件定义?

javascript - 更改父类(super class)静态构造函数的返回值