Typescript универсальный тип объединения - PullRequest
0 голосов
/ 19 сентября 2019

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

export const mySwitch = <T extends string>(value: T, possibilities: {[key in T]: any}): any => {
    return possibilities[value];
};

Типичное использование:

let option: "val1" | "val2" | "val3" = "val1";
// should returns s1
// Impossible should be type-checked as an error since it's not part of the option union type
mySwitch(option, {val1: "s1", val2: "s2", val3: "s3", impossible: "impossible"});

Моя проблема возникает из-за того, что универсальный тип T должен быть string, чтобы использоваться в качестве ключа объекта.Я не знаю, как вы можете сказать T как объединение string.

Я пытался T extends string безуспешно.

1 Ответ

1 голос
/ 19 сентября 2019

Версия T extends string, кажется, работает хорошо.Он запрещает impossible, но разве вы не хотели бы запретить его, поскольку, если параметр никогда не может иметь этого значения, эта опция была бы бесполезна?:

export const mySwitch = <T extends string>(value: T, possibilities: {[key in T]: any}): any => {
    return possibilities[value];
};


declare let option: "val1" | "val2" | "val3";
mySwitch(option, {val1: "s1", val2: "s2", val3: "s3", impossible: "impossible"}); 

play

Если вы хотите разрешить дополнительные ключи, вы можете объявить объект case отдельно (минуя проверки лишних свойств и повторно использовать объект case)


declare let option: "val1" | "val2" | "val3";
const casses = {val1: "s1", val2: "s2", val3: "s3", impossible: "impossible"}
mySwitch(option, casses); 

play

Или вы можете немного изменить свой тип, чтобы универсальный параметр типа был объектом case, и значение будет набрано как keyof T:

export const mySwitch = <T>(value: keyof T, possibilities: T): any => {
    return possibilities[value];
};


declare let option: "val1" | "val2" | "val3";
mySwitch(option, {val1: "s1", val2: "s2", val3: "s3", impossible: "impossible"}); 

play

Также лучшим вариантом будет сохранение типа из объекта case вместо использования any:

export const mySwitch = <T, K extends keyof T>(value: K, possibilities: T): T[K] => {
    return possibilities[value];
};


declare let option: "val1" | "val2" | "val3";
mySwitch(option, {val1: 1, val2: "s2", val3: "s3", impossible: false});  // returns string | number

play

Редактировать:

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

const mySwitch = <T extends Record<K, any>, K extends string>(value: K, possibilities: T & Record<Exclude<keyof T, K>, never>): any => {
    return possibilities[value];
};

let option: "val1" | "val2" | "val3" = (["val1", "val2", "val3"] as const)[Math.round(Math.random() * 2)]
mySwitch(option, {val1: "s1", val2: "s2", val3: "s3" });
mySwitch(option, {val1: "s1", val2: "s2", val3: "s3", impossible: "" }); //err on impossible

play

Обратите внимание, что, поскольку машинописный текст управляет анализом потока, вы должны убедиться, что option iэто не просто типы как фактическая константа, которую вы назначаете вместо аннотации типа, которую вы указываете

...