为什么TypeScript不能在嵌套对象中推断出通用类型?

Ant*_*deo 5 typescript

我有一个TypeScript无法推断其泛型的类型。

interface Foo<A> {
    [name: string] : {
        foo : A
    }
}

function makeFoo<A>(foo: Foo<A>) : Foo<A>{
    return foo
}

// Works fine when manually specifying the types
const manuallyTyped : Foo<string | number> = {
    a: {
        foo: '1'
    },
    b: {
        foo: 3
    }
}

// ERROR, Can't infer type as Foo<string | number>
makeFoo({
    a: {
        foo: '1'
    },
    b: {
        foo: 3
    }
})
Run Code Online (Sandbox Code Playgroud)

最初,我使用下面的类型,但是我想自己创建对象对象的值。当索引签名是平坦的时,推理就可以正常工作。

interface FlatFoo<B> {
    [name: string] : B
}

function makeFlatFoo<B>(bar: FlatFoo<B>): FlatFoo<B>{
    return bar
}

// Correctly has type FlatFoo<string | number>
const inferred = makeBar({
    a: 'a',
    b: 2
})
Run Code Online (Sandbox Code Playgroud)

是否有人对此有解释和/或建议?

Mat*_*hen 5

这是一个与this questionthis question类似的问题。当 TypeScript 对同一个类型参数(在第一个示例中,number以及stringfor A)进行多个协变推断时,它会尝试选择其中一个是其他类型参数的超类型;它不会推断联合,除非在推断是相同基本类型的文字类型的特殊情况下。如果 TypeScript 在其他情况下似乎推断出联合类型,那是因为某些其他语言功能在起作用。在 的情况下makeFlatFoo,该功能是对象文字类型的隐式索引签名生成,它采用属性a和的类型的并集b,即string | numberstring | number匹配B你会得到一个单一的推论string | numberB,一切工作。但是,在 中makeFoo,隐式索引签名的返回类型是Foo<string> | Foo<number>。当它匹配时Foo<A>,联合被打破,你得到两个不同的推论stringnumberfor A

虽然以下基于您的答案的示例编译没有错误:

function makeFoo<A, F extends Foo<A>>(foo: F) : F{
    return foo
}

const result = makeFoo({
    a: {
        foo: '1'
    },
    b: {
        foo: 3
    }
});
Run Code Online (Sandbox Code Playgroud)

你会看到,A{}和类型resultIS { a: { foo: string; }; b: { foo: number; }; },所以你还没有在对象转换为一个成功Foo<T>的类型。相反,您可以使用类型参数FA来捕获隐式索引签名的返回类型,然后使用分布式条件类型来提取foo属性的实际类型,如以下答案所示

interface FlatFoo<FA> { 
    [name: string]: FA;
}
type FooPropTypes<FA> = FA extends { foo: infer A } ? A : never;
function makeFoo<FA extends {foo: unknown}>(foo: FlatFoo<FA>) : Foo<FooPropTypes<FA>> {
    return <any>foo
}
Run Code Online (Sandbox Code Playgroud)