Типы для функции, которая применяет имя функции и аргументы - PullRequest
2 голосов
/ 12 февраля 2020

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

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' )

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

1006 * Ссылка на Playgound: http://www.typescriptlang.org/play/?ssl=22&ssc=1&pln=23&pc=1#code / MYewdgzgLgBBCuBbGBeGAKAhgLhmJARgKYBOANDAbvosSQJSoB8MmMA1JQFCiSy-BMsNFlzQSASzABzClThRJMisDGKp0xihZtOBDjGBce4aDABm8SKhgBvLjEdwkZB04FCuAX2NQAngAORDAA0kR + EDYA5AiIUTAAPjBRHlBRvoHBAIIk0hAAPAAqLGiFMEQAHlBEYAAmkegAdM2YuRC4UuakMABKWjpgfjAA-L0w1EQAbqQZQTAAykg2OXn5-kEg5s6ITFzrwQDC4ILCMCsF + 5uGx0K7XJZgwFAS4KwBAQA2fvkh5VU19VC4QgTHQDzCflwIQozUarTyuHOa0yVweEAA2iEALpMRj2JwwEhEKDwEhgCxWDHg8JYpotNr0bzGXhmarQACMNkw7y + 6BiSCiFHZFAATIyWbA2VARVyeX4 + alBclxOylTFFCK1eIAMzxRlAA

1 Ответ

2 голосов
/ 12 февраля 2020

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

Хорошо, надеюсь, это поможет; удачи!

Детская площадка ссылка на код

...