Comments 11
Предложение зачтено, но, что первое в голову пришло, не проще для этих целей использовать enum ? И никаких функций не нужно.
enum Data {
foo = "foo",
bar = "bar",
none = null,
}
const check = (data) => {
switch (data) {
case Data.foo:
alert("foo");
break;
case Data.bar:
alert("bar");
break;
case Data.none:
alert("none");
break;
default:
alert("default");
break;
}
};
const inputKey = "sdfsdf";
check(inputKey);
В рамках задачи, конкретно этой, такой подход проще вроде ) В любом случае ваш кому-то понравится)
Спасибо за работу!
В рамках примера да, проще использовать enum. Но пример очень упрощён.
Если рассматривать вопрос шире, то enum не поможет, если потребуется, например дженерик или объектный тип.
В случае с объектным типом, для сбора утилит в одном месте подошёл бы и класс со статичными методами. Но он же не подошёл бы для примера с union type в статье.
Мне тем и нравится подход с воплощёнными типами, что он позволяет эффективно и единообразно решить проблему для всех возможных видов типов.
А зачем вам такой объект? Модуль - уже и есть готовый объект с таким же предназначением. Разве так не проще?
strbool.mts
:
export const foo = 'foo';
export type Foo = typeof foo;
export const bar = 'bar';
export type Bar = typeof bar;
type StrBool = Foo | Bar | null;
export { type StrBool as default };
export function is(value: unknown): value is StrBool {
return isFoo(value) || isBar(value) || isNull(value);
}
export function isFoo(value: unknown): value is Foo {
return value === foo;
}
export function isBar(value: unknown): value is Bar {
return value === bar;
}
export function isNull(value: unknown): value is null {
return value === null;
}
export function toBoolean(value: StrBool): boolean {
return isFoo(value) || isNull(value);
}
Использование:
import type StrBool from './strbool.mjs';
import { is, isBar, isFoo } from './strbool.mjs';
1) Так "жирнее" импорты, может быть ощутимо в файле где используется много типов.
2) Если у вас будет много типов, то получится та самая проблема, которую я предлагаю решить. Либо у вас везде type guard-ы называются is и тогда intellisense вам не поможет, либо каждый type guard в имени будет содержать имя типа, тогда опять же вспоминать названия и ещё "жирнее" импорты.
Это все может не быть проблемами в небольших проектах, но с увеличением кодовой базы начнет сказываться.
export type StrBool = 'foo'& {_strbooltag?: 'StrBool'} | 'bar' & {_strbooltag?: 'StrBool'} | null;
export type Response = { decision: Exclude<string, 'foo'|'bar'> | StrBool };

Можно как-то так сделать
Можно, но это:
1) не решает проблему с разрозненными утилитами;
2) намного ближе к теме номинальных типов, её я не планировал затрагивать в данной статье.
Кстати, номинальный тип можно определить как воплощённый (в соответствии с терминологией из статьи) и так с ним будет работать намного удобнее :)
Вообще когда я только начал изучать TS я ожидал что type guards позволяют вот так делать. Все еще мечтаю что добавят. С другой стороны typeof так умеет
function getType(x: unknown):
x is { name: string } ? 'user' :
x is { size: number } ? 'box' :
x is number ? 'number' :
x is string ? 'string' :
never {
???
}
switch(getTupe(x)) {
case 'user': log(x.name);
case 'box': log(x.size);
case 'number': log(x * x);
case 'string': log(x);
}
Для такого в ТС уже есть неймспейсы
type Foo = Something
namespace Foo {
export function util() {}
export type NestedType = Something
export const value = something
}
В них удобно прятать утилиты и детали реализации подобных вещей.
Кстати, помимо "воплощённых" типов ещё можно городить воплощённые функции:
function f() {}
namespace f {
export type Util = Something
export const value = something
}
И даже воплощённые классы:
class User { }
namespace User {
export type NestedType = Something
}
Касательно примера в статье:
export type StrBool = 'foo' | 'bar' | null;
export type Response = { decision: string | null };
// Можно написать и decision: string | StrBool,
// но typescript все равно сведет это к типу string | null
Чтобы юнион не схлопывался можно записать так: StrBool | (string & {})
Согласен, с namespace-ами получится +- то же самое. Тут уже дело вкуса, как мне кажется. Статья в целом про подход, я материалов и примеров про такое их использование не нашёл.
Юнион не схлопнется, но и смысла в нём не прибавится, т.к. все равно string уже включает в себя значения 'foo' и 'bar'.
А это не является реализацией шаблона "стратегия" или чего-то похожего?
Воплощённые типы