Pull to refresh

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

Юнион не схлопнется, но и смысла в нём не прибавится, т.к. все равно string уже включает в себя значения 'foo' и 'bar'.

Смысл в том что эти литералы будут отображаться в подсказках

А это не является реализацией шаблона "стратегия" или чего-то похожего?

Sign up to leave a comment.

Articles