Компилятор TS недоволен типом объединения, включающим в себя бренд enum (номинальная типизация) - PullRequest
1 голос
/ 05 апреля 2019

Я использую enum для достижения номинальной типизации (как, например, предложено в TypeScript Deep Dive книга):

enum ExampleIdBrand {}
export type ExampleId = ExampleIdBrand & string
const exampleId: ExampleId = '42' as ExampleId

const m1 = (e: ExampleId) => e.toUpperCase()
m1(exampleId) // ✅

Пока все работает так, как ожидалось.Однако, если я изменю метод, чтобы принять (более широкий) тип объединения, компилятор больше не принимает мои exampleId:

const m2 = (e: ExampleId | 'whatever') => e.toUpperCase()
m2('whatever') // ✅
m2(exampleId) // ? Does not compile

Почему последняя строка компилируется просто отлично?(TS 3.3.4000)

Ответы [ 2 ]

1 голос
/ 05 апреля 2019

То, что происходит с пересечением, которое является пустым набором (т. Е. Нет значений, которые могут быть экземплярами пересечения), - это то, что изменилось. Я не могу действительно найти документы, хотя я буду продолжать искать, но в определенных условиях такие пересечения никогда не разрушатся. И мы видим это на работе в этом случае, ExampleId | 'whatever' = never | 'whatever' = 'whatever'

const m2 = (e: ExampleId | 'whatever') => e.toUpperCase()
type x =  Parameters<typeof m2>[0] //  'whatever'

Чтобы сохранить номинальный характер ExampleId, мы можем добавить свойство вместо:

enum ExampleIdBrand {}
export type ExampleId = { __brand: ExampleIdBrand } & string
const exampleId: ExampleId = '42' as ExampleId

const m1 = (e: ExampleId | "whatever") => e.toUpperCase()
m1(exampleId) // ✅
m1("whatever")// ✅

Или, если мы хотим действительно хорошо скрыть этот член, мы можем использовать пересечение с классом с закрытым полем:

enum ExampleIdBrand { }
class Brand<T> { private __brand: T}
export type ExampleId = Brand<ExampleIdBrand> & string
const exampleId: ExampleId = '42' as ExampleId

const m1 = (e: ExampleId | "whatever") => e.toUpperCase()
m1(exampleId) // ✅
m1("whatever")// ✅

Или бросьте перечисление и используйте это class ExampleIdBrand { private __brand!: any}

1 голос
/ 05 апреля 2019

В TypeScript перечисления могут иметь номер типа или строку.У числа нет метода .toUpperCase ().

Ваш пример должен работать, потому что перечисление сужено до типа string.

Обходной путь будет:

enum ExampleIdBrand {}
export type ExampleId = ExampleIdBrand;
const exampleId: ExampleId = '42';

const m1 = (e: ExampleId) => e.toString().toUpperCase();
m1(exampleId);

const m2 = (e: ExampleId | 'whatever') => e.toString().toUpperCase();
m2('whatever'); // ✅
m2(exampleId); // ✅
...