Компилятор не сможет понять, что это безопасно для типов, потому что он обычно не очень хорошо рассуждает о присваиваемости для типов, которые зависят от пока еще неуказанных параметров типа generi c. Существует существующая проблема GitHub, microsoft / TypeScript # 13995 , которая описывает эту ситуацию.
На самом деле, возможно (но не очень вероятно), что в вашей функции K
может быть выведено как Keys
вместо "sum"
или "concat"
. Если вы сделаете это:
const oops = apply(Math.random() < 0.5 ? "sum" : "concat", "a", "b", "c"); // oopsie
console.log(oops); // 50% chance of "abc", 50% chance of "ab"
, то вы увидите, что компилятор технически правильно, что то, что вы делаете, является типобезопасным. Вы хотели бы сообщить компилятору, что K
будет точно одним из членов Keys
, а вы не можете. См. microsoft / TypeScript # 27808 для предложения функции, которая бы позволила это.
В любом случае, компилятор не может просматривать параметр funKey
и параметр args
rest как имеющие коррелированных типов. И даже если это возможно, это не очень хорошо для поддержания корреляции, подробнее об этом см. microsoft / TypeScript # 30581 .
Он также не может понять вычисление возвращаемого типа, так что вы придется аннотировать. Для этого вы можете использовать ReturnType<F>
тип утилиты . Обратите внимание, что есть также Parameters<F>
тип утилиты , который вы можете использовать вместо того, чтобы писать Args<F>
самостоятельно.
Так что, когда дело доходит до этого, вам просто нужно скажите компилятору, что то, что вы делаете, является типобезопасным (вы не будете вызывать apply()
для некоторых funKey
с типом union, верно?), потому что он не может это проверить. И для этого вам нужно что-то вроде утверждения типа . Здесь проще всего использовать старый добрый any
:
type Funs = typeof funs;
function apply<K extends Keys>(funKey: K, ...args: Parameters<Funs[K]>): ReturnType<Funs[K]> {
return (funs[funKey] as any)(...args);
}
Это позволит вам делать сумасшедшие вещи, такие как return (funs[funKey] as any)(true)
, поэтому вам следует быть осторожным. Чуть более безопасным для типов, но значительно более сложным является представление funs[funKey]
как функции, которая так или иначе принимает либо аргументы, ожидаемые каждой функцией, и которая возвращает оба возвращаемых типов. Вот так:
type WidenFunc<T> = ((x: T) => void) extends ((x: (...args: infer A) => infer R) => any) ?
(...args: A) => R : never;
function apply<K extends Keys>(funKey: K, ...args: Parameters<Funs[K]>): ReturnType<Funs[K]> {
return (funs[funKey] as WidenFunc<Funs[Keys]>)(...args);
}
Здесь WidenFunc<Funs[Keys]>
равно (...args: [number, number] | [string, string, string]) => number & string
. Это своего рода бессмысленный тип функции, но, по крайней мере, он будет жаловаться, если вы передадите ему аргумент типа (true)
вместо (...args)
.
В любом случае, любой из них должен работать:
const test1 = apply('sum', 1, 2) // number
const test2 = apply('concat', 'str1', 'str2', 'str3') // string
Хорошо, надеюсь, это поможет; удачи!
Детская площадка ссылка на код