我正在尝试从传入的参数的泛型中推断方法的返回类型。但是,该参数是来自泛型接口的实现,因此我假设打字稿推断将根据参数的类型来确定类型。基地。
示例代码:
interface ICommand<T> {}
class GetSomethingByIdCommand implements ICommand<string> {
constructor(public readonly id: string) {}
}
class CommandBus implements ICommandBus {
execute<T>(command: ICommand<T>): T {
return null as any // ignore this, this will be called through an interface eitherway
}
}
const bus = new CommandBus()
// badResult is {}
let badResult = bus.execute(new GetSomethingByIdCommand('1'))
// goodResult is string
let goodResult = bus.execute<string>(new GetSomethingByIdCommand('1'))
我想做的是第一次execute
调用,并让打字稿推断正确的返回值,在这种情况下,该返回值是string
,它基于实现GetSomethingByIdCommand
的结果。
我尝试过使用conditional types,但不确定这是解决方案还是应用方法。
答案 0 :(得分:8)
您的问题是ICommand<T>
在结构上不依赖于T
(如@CRice的评论中所述)。
这是not recommended。 (⬅链接到TypeScript FAQ条目,其中详细说明了一个与此案例几乎完全相同的案例,因此与我们可能在此处得到的官方用语一样接近)
TypeScript的类型系统(主要)是结构性的,而不是名义上的:两个类型是相同的,当且仅当它们具有相同的形状(例如,具有相同的属性),并且与它们是否具有相同的名称无关。如果ICommand<T>
不是结构上地依赖于T
,并且其属性均与T
没有关系,则ICommand<string>
是< em>与ICommand<number>
相同的类型,与ICommand<ICommand<boolean>>
相同的类型,ICommand<{}>
与execute()
相同。是的,这些都是不同的 names ,但是类型系统不是名义上的,因此没有多大用处。
在这种情况下,您不能依靠类型推断来工作。当您调用T
时,编译器会尝试在ICommand<T>
中为{}
推断类型,但是没有任何可推断的类型。因此,它最终默认为空类型ICommand<T>
。
此问题的解决方法是使T
在某种程度上在结构上依赖于ICommand<Something>
,并确保实现interface ICommand<T> {
id: T;
}
的任何类型都能正确执行。根据您的示例代码,执行此操作的一种方法是:
ICommand<T>
因此,id
必须具有类型T
的{{1}}属性。幸运的是,GetSomethingByIdCommand
实际上确实具有id
所要求的string
类型的implements ICommand<string>
属性,因此可以很好地进行编译。
而且,重要的是,您确实要进行推断:
// goodResult is inferred as string even without manually specifying T
let goodResult = bus.execute(new GetSomethingByIdCommand('1'))
好的,希望能有所帮助;祝好运!
答案 1 :(得分:2)
如果在将具体类型传递给ICommandBus.execute()
之前,将具体类型强制为其泛型等效,则Typescript似乎能够正确推断类型:
let command: ICommand<string> = new GetSomethingByIdCommand('1')
let badResult = bus.execute(command)
或者:
let badResult = bus.execute(new GetSomethingByIdCommand('1') as ICommand<string>)
这不是一个完美的解决方案,但它可以工作。显然,打字稿泛型不是很完整。
答案 2 :(得分:1)
TS无法推断出该方法正在以您希望的方式实现的接口。
这里发生的是,当您使用以下方法实例化新类时:
new GetSomethingByIdCommand('1')
实例化一个新类的结果是一个对象。因此,execute<T>
将返回一个对象,而不是您期望的字符串。
在execute函数返回结果之后,您将需要进行类型检查。
对于对象vs字符串,您只需执行typeof检查。
const bus = new CommandBus()
const busResult = bus.execute(new GetSomethingByIdCommand('1'));
if(typeof busResult === 'string') {
....
}
当将Typescript编译为纯JS时,这在运行时效果很好。
如果是对象或数组(它们也是对象:D),则应使用类型保护。
类型保护器会尝试将项目强制转换为某种对象,并检查属性是否存在并推断使用的是哪种模型。
interface A {
id: string;
name: string;
}
interface B {
id: string;
value: number;
}
function isA(item: A | B): item is A {
return (<A>item).name ? true : false;
}