То, что происходит с пересечением, которое является пустым набором (т. Е. Нет значений, которые могут быть экземплярами пересечения), - это то, что изменилось. Я не могу действительно найти документы, хотя я буду продолжать искать, но в определенных условиях такие пересечения никогда не разрушатся. И мы видим это на работе в этом случае, 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}