无法推断扩展接口的通用类型

时间:2018-11-23 14:01:16

标签: typescript generics types type-inference

在以下示例中,Typescript可以根据T中传递给它的参数来推断方法foobar的类型,但不能推断{{1 }},因为它知道R的类型以及T

T extends I<R>

还有另一种方法吗?

3 个答案:

答案 0 :(得分:1)

第一个问题是您的接口是空的,打字稿使用结构化类型,因此,如果您的通用接口不使用它的type参数,那么拥有它并不重要。例如,这有效:

interface I<T> { }
declare let foo: I<string> 
declare let bar: I<number>
// Same structure ({}), assignment works
foo = bar
bar = foo 

如果我们添加一个字段,Typescript仍然不会推断R类型参数,只是不会尝试从T中提取它。最好的选择是使用条件类型并在需要的地方提取泛型类型参数:

interface I<T> {
    value :T 
}

class A implements I<string> {
    value! :string 
}

type ExtractFromI<T extends I<unknown>> = T extends I<infer U> ? U : never;
function foo<T extends I<unknown>>(bar: T):  ExtractFromI<T>{
    return bar.value as ExtractFromI<T>; // Generic type with unresolved type parameters, we need a type assertion to convince the compiler this is ok 
}
var r = foo(new A()); // string

答案 1 :(得分:0)

您可能还只想从实现中提取它。

例如

export class CatWatcher extends Watcher<Cat>
{
    watch(): Cat { ... }  // overridden from base
}

我想从Cat获得CatWatcher。正如其他答案所言,似乎不可能通过推断从基类中提取它。

但是您知道它会有一个watch函数-因此从那里提取它。

export type ExtractWatcherType<T> = T extends { watch: infer W } ? W : never;

答案 2 :(得分:0)

我想补充@Titan接受的答案。您还可以在界面上添加一个隐藏的Symbol来欺骗结构化类型。

我在项目中使用了以下内容,并且编译器能够推断出Action的Result类型。您可以在Typescript Playground中进行尝试。

const F = Symbol();

interface Action<R = unknown> {
    [F]?: R; // this trick the compiler
}

type ActionResult<T extends Action> = T extends Action<infer R> ? R : never;

interface SetName extends Action<SetNameResult> {
    type: 'set_name';
    name: string;
}

interface SetNameResult {
    type: 'set_name_result';
    newName: string;
}

function process<A extends Action>(action: A): ActionResult<A> {
    return {} as any; // TODO process action and return a valid result
}

const r = process({ type: 'set_name', name: 'the name' } as SetName);
r.type; // 'set_name_result'
r.newName; // string