TypeScript: предотвращение перекрытия членов объединения

#typescript

#typescript

Вопрос:

Я пытаюсь определить тип, который представляет собой объединение многих строк объединения.

пример.

 type SubUnion1 = "one" | "two"
type SubUnion2 = "two" | "three"
...
type SubUnionN = "nine" | "ten"

type Union = SubUnion1 | SubUnion2 | ... | SubUnionN
  

Я бы хотел, чтобы TS выдавал ошибку, если какое-либо из подразделений имеет перекрывающиеся строки. В этом примере SubUnion1 и SubUnion2 перекрывается "two" , что должно вызвать ошибку.

Я знаю, что это можно сделать между 2 типами, используя что-то вроде:

 type NoOverlap<A, B> = Extract<A, B> extends never ? A amp; B : never
  

Тем не менее, я не могу придумать изящный способ сделать это более чем на 2 типа.

Ответ №1:

Я не знаю, считаете ли вы этот подход «изящным», но, по крайней мере, он не требует, чтобы вы сами выписывали каждую пару SubUnionX элементов:

Сначала создайте кортеж из объединений, чтобы компилятор мог обрабатывать их программно, прежде чем вы соберете Union и потеряете отслеживание различных объединений:

 type SubUnions = [SubUnion1, SubUnion2, /* ... */ SubUnionN]
  

Затем мы можем создать тип, который вычисляется never , если нет перекрытий, и в противном случае он создает объединение кортежей с информацией о том, где находятся перекрытия:

 type Overlaps<T extends any[]> = { [K in keyof T]: { [L in keyof T]:
    L extends K ? never :
    (T[K] amp; T[L]) extends never ? never :
    ["Elements at indices", K | L, "both contain", T[K] amp; T[L]]
}[number] }[number]
  

По сути, мы дважды сопоставляем кортеж, чтобы получить матрицу N на N. Мы смотрим на каждый элемент с индексом ( K , L ), и если T[K] и T[L] перекрываются, то мы помещаем запись в матрицу. Затем мы получаем все записи в матрице как объединение.

Обратите внимание, что ["Elements at indices", K | L, "both contain", T[K] amp; T[L]] технически запись представляет собой просто тип кортежа с четырьмя элементами. Первый и третий элементы являются строковыми литеральными типами, а второй и четвертый являются перекрывающимися типами ключей и перекрывающихся свойств соответственно. Этот тип действительно бесполезен как тип любого значения во время выполнения. Мы не думаем о создании массива из четырех элементов, который выглядит так. На самом деле это просто что-то, изложенное таким образом, что, как мы надеемся, выдаст полезное сообщение об ошибке. Другими словами, это временный «недопустимый тип» (функция, которая в настоящее время не существует в TypeScript, но в конечном итоге может быть реализована как что-то вроде throw типов).

Наконец, если вам нужна фактическая ошибка компилятора при наличии перекрытий, мы можем создать функцию типа, которая принимает только never .

 type ExpectNever<T extends never> = void;
  

Наконец, мы собрали все это вместе следующим образом:

 type EnsureNoOverlapsOfSubUnions = ExpectNever<Overlaps<SubUnions>>; // error!
// Type '["Elements at indices", "0" | "1", "both contain", "two"]' 
// does not satisfy the constraint 'never'.
  

Надеюсь, этого сообщения об ошибке достаточно, чтобы помочь устранить проблему.

О, и если у вас есть SubUnions как кортеж, вы можете получить свой оригинал Union , просто получив его number индексированные свойства:

 type Union = SubUnions[number];
  

Ссылка на игровую площадку для кода

Комментарии:

1. На самом деле, одна небольшая просьба! Не могли бы вы указать мне на документацию о том, что происходит в этой строке? ["Elements at indices", K | L, "both contain", T[K] amp; T[L]] Это то, о чем я не знал, что это возможно!

2. Я добавил некоторую информацию о том, что это такое. Это тип, который выдает сообщение об ошибке. Компилятор считает, что «это тип кортежа из четырех элементов», но, надеюсь, когда человек читает сообщение в своей IDE, они интерпретируют ["Elements at indices", "0" | "1", "both contain", "two"] как одну строку, например "Elements at indices 0 and 1 both contain 'two'" .