Компилятор не сможет понять, что это типобезопасный, потому что он обычно не очень хорошо рассуждает о возможности назначения для типов, которые зависят от еще не определенных параметров универсального типа. Существует существующая проблема GitHub, microsoft / TypeScript # 24085, которая описывает эту ситуацию.
Фактически, возможно (но маловероятно), что в вашей функции 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
с объединенным типом, верно?), Потому что он не может это проверить. А для этого вам понадобится что-то вроде утверждения типа. Здесь проще всего использовать старый добрый 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
Хорошо, надеюсь, что это поможет; удачи!
площадка ссылка на код
person
jcalz
schedule
12.02.2020