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