我正在定义一个类,它通常将一种对象类型转变为另一种对象类型,它有一个非常简单的接口(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 .因此,请为此做好准备。
您的类型定义缺少的一件事是 IFooBarData
和 IBazBarData
有type
类型的属性 string
,但您的实现只有在缩小到正确的 "foobar"
时才有效和 "bazbar"
string literal types ;否则有人可以制作 IFooBarData
用type
类似"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
持有 FooBar
那typeAssemblers.oopsie
不存在。相反,我建议不要注释 typeAssemblers
根本上,为它推断一个更窄的类型。我们也可以为缩小的类型命名:
const typeAssemblers = {
foobar: FooBar,
bazbar: BazBar
}
type TypeAssemblers = typeof typeAssemblers;
此类型被编译器推断为 {foobar: typeof FooBar; bazbar: typeof BazBar;}
.
最后,Assembler
类实际上应该只有一个泛型类型参数。 In
之间的关系类型和 Out
类型由 TypeAssemblers
设置;不应允许用户询问 In
正在IBazBarData
同时Out
是 FooBar
.所以我们只使用 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
看起来不错,可以编译;编译器知道 assembly
是 FooBar
.
好的,希望对您有所帮助;祝你好运!
关于typescript - 我如何为扩展简单接口(interface)的通用输出键入一个类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59779249/