Типы для функции, которая применяет имя функции и аргументы

Я пытаюсь правильно ввести функцию, которая применяет имя функции и аргументы для этой функции. После этого примените его и верните результат. Вот код:

const sum = (a: number, b: number) => a + b
const concat = (a: string, b: string, c: string) => a + b + c

const funs = {
    sum,
    concat
}

type Keys = 'sum' | 'concat'

type Args<T> = T extends (...args: infer R) => any ? R : never

type Sum = Args<typeof sum>
type Concat = Args<typeof concat>

function apply<K extends Keys>(funKey: K, ...args: Args<typeof funs[K]>) {
    // here I get the error 'An argument for 'a' was not provided.'
    return funs[funKey](...args)
}

const test1 = apply('sum', 1, 2)
const test2 = apply('concat', 'str1', 'str2', 'str3' )

Внутри funciton apply я получаю сообщение об ошибке «Аргумент для« a »не был предоставлен.». Как мне избавиться от этой ошибки?

Ссылка на игру


person Ivan    schedule 12.02.2020    source источник


Ответы (1)


Компилятор не сможет понять, что это типобезопасный, потому что он обычно не очень хорошо рассуждает о возможности назначения для типов, которые зависят от еще не определенных параметров универсального типа. Существует существующая проблема 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