В следующем я предполагаю, что мы используем TS v3.1:
Похоже, что "OneOf" означает "должен соответствовать точно один", тогда как "AnyOf" означает "долженсоответствует как минимум один ".Оказывается, что «хотя бы один» является более базовой концепцией и соответствует операции union (« включительно или »), представленной символом |
.Таким образом, ответ на ваш вопрос таков:
type AnyOfSample = HasName | HasEmail // Union type
Обратите внимание, что дальнейшее объединение с пересечением ничего не меняет:
type AnyOfSample = HasName | HasEmail | (HasName & HasEmail) // same thing
, поскольку объединение может только возможноДобавьте элементы, и все элементы HasName & HasEmail
уже присутствуют в HasName | HasEmail
.
Конечно, это означает, что у вас есть неправильное определение для OneOfSample
.Эта операция больше похожа на дизъюнктивный союз ( "exclusive или" ), хотя не совсем так, потому что, когда у вас есть три или более наборов, обычное определение средства дизъюнктивного объединения "соответствуетнечетное число ", а это не то, что вы хотите.Кроме того, я не могу найти широко используемого названия для типа дизъюнктивного союза, о котором мы здесь говорим, хотя вот интересная статья , в которой это обсуждается.
Итак, какмы представляем "соответствует ровно одному" в TypeScript?Это не просто, потому что его легче всего построить с помощью отрицания или вычитания типов, чего в настоящее время TypeScript не может сделать.То есть вы хотите сказать что-то вроде:
type OneOfSample = (HasName | HasEmail) & Not<HasName & HasEmail>; // Not doesn't exist
, но здесь не работает Not
.Поэтому все, что вы можете сделать, это какой-то обходной путь ... так что же возможно?Вы можете сообщить TypeScript, что тип может не иметь определенного свойства.Например, тип NoFoo
может не иметь ключа foo
:
type ProhibitKeys<K extends keyof any> = {[P in K]?: never};
type NoFoo = ProhibitKeys<'foo'>; // becomes {foo?: never};
И вы можете взять список имен ключей и удалить имена ключей из другого списка (то есть, вычитание строковых литералов), используя условные типы:
type Subtract = Exclude<'a'|'b'|'c', 'c'|'d'>; // becomes 'a'|'b'
Это позволяет вам сделать что-то вроде следующего:
type AllKeysOf<T> = T extends any ? keyof T : never; // get all keys of a union
type ProhibitKeys<K extends keyof any> = {[P in K]?: never }; // from above
type ExactlyOneOf<T extends any[]> = {
[K in keyof T]: T[K] & ProhibitKeys<Exclude<AllKeysOf<T[number]>, keyof T[K]>>;
}[number];
В этом случае ExactlyOneOf
ожидает кортеж типови будет представлять объединение каждого элемента кортежа, явно запрещающее ключи других типов.Давайте посмотрим на это в действии:
type HasName = { name: string };
type HasEmail = { email: string };
type OneOfSample = ExactlyOneOf<[HasName, HasEmail]>;
Если мы проверяем OneOfSample
с IntelliSense, то это:
type OneOfSample = (HasEmail & ProhibitKeys<"name">) | (HasName & ProhibitKeys<"email">);
, который говорит "либо HasEmail
без name
свойство или HasName
без свойства email
. Работает ли оно?
const okayName: OneOfSample = { name: "Rando" }; // okay
const okayEmail: OneOfSample = { email: "rando@example.com" }; // okay
const notOkay: OneOfSample = { name: "Rando", email: "rando@example.com" }; // error
Похоже на это.
Синтаксис кортежа позволяет добавлять три или более типов:
type HasCoolSunglasses = { shades: true };
type AnotherOneOfSample = ExactlyOneOf<[HasName, HasEmail, HasCoolSunglasses]>;
Это проверяется как
type AnotherOneOfSample = (HasEmail & ProhibitKeys<"name" | "shades">) |
(HasName & ProhibitKeys<"email" | "shades">) |
(HasCoolSunglasses & ProhibitKeys<"email" | "name">)
, который, как вы видите, правильно распределяет запрещенные ключи вокруг.
Есть и другие способы сделать это, ноя бы так и поступил. Это обходной путь, а не идеальное решение, потому что есть ситуации, которые он не обрабатывает должным образом, например, два типа с одинаковыми ключами, свойства которых имеют разные типы:
declare class Animal { legs: number };
declare class Dog extends Animal { bark(): void };
declare class Cat extends Animal { meow(): void };
type HasPetCat = { pet: Cat };
type HasPetDog = { pet: Dog };
type HasOneOfPetCatOrDog = ExactlyOneOf<[HasPetCat, HasPetDog]>;
declare const abomination: Cat & Dog;
const oops: HasOneOfPetCatOrDog = { pet: abomination }; // not an error
В приведенном выше примере ExactlyOneOf<>
не может вернуться к свойствам свойства pet
, чтобы убедиться, что оно не является одновременно Cat
и Dog
. Это может быть решено, но оно становится более сложным, чемВы, вероятно, хотите. Есть и другие крайние случаи. Это зависит от того, что выред.
В любом случае, надеюсь, это поможет.Удачи!