如何在TypeScript中键入可配置的工厂函数?

时间:2019-10-13 17:02:08

标签: javascript typescript generics factory

我正在尝试为工厂函数工作的基本示例,该函数根据传递给它的key字符串返回各种返回类型…

const factory = <
    T extends Record<string, () => any>,
    K extends keyof T
>(options: T) => {
    return (key: K): ReturnType<T[K]> => {
        return options[key]()
    }
}

const create = factory({
    string: () => 'A string of text.',
    number: () => 42,
    boolean: () => true
})

const s = create('string')
const n = create('number')
const b = create('boolean')

但是在这种情况下,snb的类型均为string | number | boolean。而不是每个类型都有最窄的类型。 (TypeScript Playground link

如何进行收窄工作?

1 个答案:

答案 0 :(得分:1)

调用泛型函数时,此时将指定其类型参数。这可以由调用者使用尖括号(例如factory<MyType, MyKey>(someValueOfMyType))手动完成,也可以由编译器根据传递给函数的参数以及调用函数的上下文来推断出来。根据您的定义,调用

const create = factory({
    string: () => 'A string of text.',
    number: () => 42,
    boolean: () => true
});

必须推断TKT很容易推断,因为传递到create的参数是T类型的。因此T{string: ()=>string, number: ()=>number, boolean: ()=>boolean}。但是在该调用中没有任何地方可以推断出K。没有类型为K的参数,上下文只是将返回值保存到名为create的变量中。因此,推断失败。在这种情况下,编译器会选择最有效的类型,即其constraint。这意味着K被推断为keyof T,而成为"string" | "number" | "boolean"。因此create具有类型

// const create: (key: "string" | "number" | "boolean") => string | number | boolean

您不想要的。


您实际上只希望在调用T时指定factory(),并且不希望在调用K之前指定create()。例如,一旦调用create("string"),便知道K应该是"string"。而且,由于在调用函数时指定了通用函数类型参数,因此您希望create()本身是一个通用函数,其中K是其类型参数。

所以这里的解决方案是将T保留在原处,但将K泛型从外部函数移至它返回的函数:

const factory = <
    T extends Record<string, () => any>>(options: T) => {
    return <K extends keyof T>(key: K): ReturnType<T[K]> => {
        return options[key]()
    }
}

现在,当您调用factory()时,它将返回一个通用函数:

const create = factory({
    string: () => 'A string of text.',
    number: () => 42,
    boolean: () => true
})

/* const create: <K extends "string" | "number" | "boolean">(key: K) => ReturnType<{
    string: () => string;
    number: () => number;
    boolean: () => boolean;
}[K]> */

然后它会按照您的预期运行:

const s = create('string') // string
const n = create('number') // number
const b = create('boolean') // boolean

好的,希望能有所帮助。祝你好运!

Link to code