在推断泛型类型时避免扩展

时间:2020-08-26 14:50:25

标签: typescript type-inference typescript-generics

使用此示例功能

type Decoder<A, B> = (v: A) => B 

declare function test<Values, D extends Decoder<Values, unknown>>(options: {
    values: Values,
    decoder: D,
    onDecoded: (decodedValue: ReturnType<D>) => unknown
}): void;

这个想法是onDecoded输入decoder计算得到的值。但是:

test({
    values: { a: "" },
    decoder: values => values.a.length,
    onDecoded: decodedValue => {
        decodedValue // unknown
    }
})

奇怪的是,如果我在values的定义中没有使用decoder,那么decodedValue的类型正确

test({
    values: { a: "" },
    decoder: () => 42,
    onDecoded: decodedValue => {
        decodedValue // number
    }
})

这是一个带有相同示例的playground link

有没有办法使原始示例正常工作?

1 个答案:

答案 0 :(得分:2)

这里的问题是编译器在推断所有内容之前就放弃了。您只有一个对象,编译器需要从该对象推断两个类型参数,但无法一次完成所有操作。

首先,让我将您的签名重构为几乎等效的版本,以使其更易于分析:

declare function test<A, B>(options: {
    values: A,
    decoder: (a: A) => B,
    onDecoded: (b: B) => unknown
}): void;

与您的版本具有相同的推断问题,但是谈论类型要容易一些。无论如何,编译器需要从您想从中推断出AB的{​​{1}}值密码中推断出optionsA。它可以从B的类型推断出A,但是除非values的实现不依赖于B,否则可能无法推断出decoder,所以失败了。

类型推断的细节不是我的专家。但是,如果对此问题有一个规范的答案,那就是microsoft/TypeScript#38872,它使用非常相似的数据结构并遇到相同的问题。这在TypeScript中被归类为设计限制,因此在不更改A函数或调用方式的情况下,可能无法解决此问题。


更改您的调用方式将涉及向编译器提供足够的类型信息,以使其能够工作。例如,如果在调用时注释test的输入参数的类型,就可以了:

decoder

或者您可以更改test({ values: { a: "" }, decoder: (values: { a: string }) => values.a.length, // annotate onDecoded: decodedValues => { decodedValues // number } }) 的定义方式。我的一个建议是将test()对象拆分为单独的参数。相比于单个参数,编译器更愿意为不同的函数参数花费多个推理过程。也许像这样:

options

这些推论完全按照您想要的方式工作,您可以根据需要使用declare function test2<A, B>(values: A, decoder: (a: A) => B, onDecoded: (b: B) => unknown ): void; test2( { a: "" }, values => values.a.length, decodedValues => { decodedValues // number } ) test2({ a: "" }, () => 42, decodedValues => { decodedValues // number } ) D重写它们。


我猜你想走哪条路。无论如何,希望能有所帮助;祝你好运!

Playground link