接口实现的Typescript通用推断

时间:2019-03-21 21:45:16

标签: typescript typescript-generics

我正在尝试从传入的参数的泛型中推断方法的返回类型。但是,该参数是来自泛型接口的实现,因此我假设打字稿推断将根据参数的类型来确定类型。基地。

示例代码:

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,但不确定这是解决方案还是应用方法。

3 个答案:

答案 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;
}