Когда вы вызываете обобщенную функцию, параметры ее типа указываются в это время. Это либо делается вызывающей стороной вручную с использованием угловых скобок (например, factory<MyType, MyKey>(someValueOfMyType)
), либо компилятор выводит их из параметров, переданных функции и контексту, в котором вызывается функция. По вашему определению, вызов
const create = factory({
string: () => 'A string of text.',
number: () => 42,
boolean: () => true
});
должен выводить T
и K
. T
легко вывести, потому что параметр, переданный в create
, имеет тип T
. Так что T
это {string: ()=>string, number: ()=>number, boolean: ()=>boolean}
. Но нигде в этом призыве нет места, где можно сделать вывод K
. Нет параметра типа K
, и контекст просто сохраняет возвращаемое значение в переменную с именем create
. Таким образом, вывод не выполняется. В таких случаях компилятор выбирает самый широкий тип, который работает, а именно его ограничение . Это означает, что 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
Хорошо, надеюсь, это поможет. Удачи!
Ссылка на код