Ответ 1
Если в вашей программе есть несколько определений объединения строк, которые вы хотите проверить во время выполнения, вы можете использовать универсальную функцию StringUnion
для генерации их статических типов и методов проверки типов.
Общая вспомогательная функция
// TypeScript will infer a string union type from the literal values passed to
// this function. Without 'extends string', it would instead generalize them
// to the common string type.
export const StringUnion = <UnionType extends string>(...values: UnionType[]) => {
Object.freeze(values);
const valueSet: Set<string> = new Set(values);
const guard = (value: string): value is UnionType => {
return valueSet.has(value);
};
const check = (value: string): UnionType => {
if (!guard(value)) {
const actual = JSON.stringify(value);
const expected = values.map(s => JSON.stringify(s)).join(' | ');
throw new TypeError('Value '${actual}' is not assignable to type '${expected}'.');
}
return value;
};
const unionNamespace = {guard, check, values};
return Object.freeze(unionNamespace as typeof unionNamespace & {type: UnionType});
};
Пример определения
Нам также нужна строка шаблона для извлечения сгенерированного типа и объединения его определения с его объектом пространства имен. Если это определение экспортируется и импортируется в другой модуль, они получат объединенное определение автоматически; потребителям не нужно будет повторно извлекать тип самостоятельно.
const Race = StringUnion(
"orc",
"human",
"night elf",
"undead",
);
type Race = typeof Race.type;
Пример использования
Во время компиляции тип Race
работает так же, как если бы мы обычно определяли объединение строк с помощью "orc" | "human" | "night elf" | "undead"
"orc" | "human" | "night elf" | "undead"
"orc" | "human" | "night elf" | "undead"
. У нас также есть .guard(...)
которая возвращает значение того, является ли значение членом объединения и может ли он использоваться в качестве защитника типов, и .check(...)
которая возвращает переданное значение, если это допустимо, иначе выдает TypeError
.
let r: Race;
const zerg = "zerg";
// Compile-time error:
// error TS2322: Type '"zerg"' is not assignable to type '"orc" | "human" | "night elf" | "undead"'.
r = zerg;
// Run-time error:
// TypeError: Value '"zerg"' is not assignable to type '"orc" | "human" | "night elf" | "undead"'.
r = Race.check(zerg);
// Not executed:
if (Race.guard(zerg)) {
r = zerg;
}
Более общее решение: runtypes
Этот подход основан на библиотеке runtypes, которая предоставляет аналогичные функции для определения практически любого типа в TypeScript и автоматического получения средств проверки типов во время выполнения. Это было бы немного более многословно для этого конкретного случая, но подумайте об этом, если вам нужно что-то более гибкое.
Пример определения
import {Union, Literal, Static} from 'runtypes';
const Race = Union(
Literal('orc'),
Literal('human'),
Literal('night elf'),
Literal('undead'),
);
type Race = Static<typeof Race>;
Пример использования будет таким же, как указано выше.