вывод машинописного текста без перечисления всех возможностей - PullRequest
1 голос
/ 05 февраля 2020

Я хотел бы знать, есть ли решение для следующей проблемы: у меня есть две функции, которые принимают разные входные параметры, у меня есть объект, отображающий каждую функцию в строку.

Я определяю тип, который тип объединения объекта, содержащий ключ одной функции на карте и значение, соответствующее типу параметров функции. Учитывая объект с этим типом, я хотел бы получить соответствующую функцию из объекта карты и вызвать ее со значением, но машинописный текст не делает вывод типа.

const f1 = (value: number) => value;
const f2 = (value: string) => value;

const map = {
    f1,
    f2
};

type MyType = {
    fun: "f1";
    value: Parameters<typeof map["f1"]>;
} | {
    fun: "f2";
    value: Parameters<typeof map["f2"]>;
};

// does not work
const test1 = (b: MyType) => {
    map[b.fun].apply(null, b.value);
};

// works
const test2 = (b: MyType) => {
    if (b.fun === "f1") {
        map[b.fun].apply(null, b.value);
    } else {
        map[b.fun].apply(null, b.value);
    }
};

Возможно ли сделать что-то вроде это без необходимости перечислять все возможности в переключателе или если / иначе?

1 Ответ

3 голосов
/ 05 февраля 2020

Вы столкнулись с менее чем звездной поддержкой TypeScript для того, что я называю коррелированными типами записей . Прямо сейчас он видит, что b.fun является типом объединения "f1" | "f2", а b.value является типом объединения [string] | [number], и он обрабатывает их как некоррелированные . Обычно объединение функций может быть вызвано только с пересечением его параметров , которое в этом случае будет [string] & [number]. Поскольку [string] | [number] нельзя присвоить [string] & [number], компилятор жалуется. Фактически никакое значение не может быть присвоено [string] & [number], поскольку его первый элемент должен быть string & number, что эквивалентно never.

, чтобы компилятор мог проверить, что map[b.fun].apply(null, b.value) является типом безопасно, по сути, потребуется анализировать код несколько раз, по одному для каждого возможного типа b. Вы можете сделать это явно, дублируя код в операторе switch / case или if / else, но это не происходит автоматически (что имеет смысл, поскольку это приведет к экспоненциальному увеличению времени компиляции, так как количество значений типа union в коде увеличивается), и вы даже не можете попросить компилятор сделать это на добровольной основе .

На данный момент единственный разумный обходной путь - это признать, что вы знаете больше, чем компилятор о безопасности типов кода, и использовать утверждение типа или эквивалент, чтобы сообщить компилятору не беспокойтесь об этом, например:

type MapVals = typeof map[keyof typeof map];
type LooselyTypedMapFunction = (...x: Parameters<MapVals>) => ReturnType<MapVals>
const test1 = (b: MyType) => {
  (map[b.fun] as LooselyTypedMapFunction).apply(null, b.value);
};

Здесь тип LooseLyTypedMapFunction в конечном итоге принимает типы f1 и f2 и объединяет их в одну функцию, которая принимает и производит string | number. И мы используем утверждение типа от map[b.fun] as LooselyTypedMapFunction до l ie немного для компилятора. Мы говорим, что map[b.fun] примет string, а также number. Конечно, это неверно, но не столкнется с проблемой, пока вы перейдете в коррелированный список b.value, а не в какую-нибудь случайную вещь, такую ​​как `[Math.random () <0.5? "упс": 123]). </p>

Это утверждение типа может оказаться более трудоемким, чем вы хотите, но, по крайней мере, оно может заразиться, если вы передадите что-то слишком безумное в map[b.fun], так как [string] | [number] по крайней мере запретит [{foo: string}]. Вы можете использовать менее безопасное утверждение, подобное этому:

const test2 = (b: MyType) => {
  (map[b.fun] as Function).apply(null, b.value);
};

, которое не требует жонглирования типа LooselyTypedMapFunction, но также будет принимать (map[b.fun] as Function).apply(null, [{foo: 123}]);. Так что будьте осторожны.


Хорошо, надеюсь, это даст вам некоторое направление. Удачи!

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

...