Мы создадим объединение всех возможностей:
type Example = {
key1: number,
key2: string
}
type PickOne<T> = { [P in keyof T]: Record<P, T[P]> & Partial<Record<Exclude<keyof T, P>, undefined>> }[keyof T]
type Example2 = PickOne<Example>;
const a: Example2 = { key3: 'a' } // incorrect
const b: Example2 = { key1: 'a' } // incorrect
const c: Example2 = { key2: 1 } // incorrect
const d: Example2 = { key1: 1 } // correct
const e: Example2 = { key2: 'a' } // correct
const f: Example2 = { key1: 1, key2: 'a' } // incorrect
То, как мы это делаем, это то, что мы сначала создаем новый тип, который для каждого ключа, у нас есть свойство объекта только с этим ключом (пока игнорируем & Partial<Record<Exclude<keyof T, P>, undefined>>
). Так { [P in keyof T]: Record<P, T[P]> }
например будет:
type Example2 = {
key1: Record<"key1", number>;
key2: Record<"key2", string>;
}
Затем используйте операцию индекса [keyof T]
, чтобы получить объединение всех значений в этом новом типе, поэтому мы получим Record<"key1", number> | Record<"key2", string>
Этот тип будет работать для всех, кроме последнего теста, где вы хотите запретить использование нескольких свойств из исходного типа. Из-за способа, которым избыточные проверки свойств работают с типами объединения ( см. ), он разрешит ключ, если он присутствует в любом из компонентов объединения.
Чтобы исправить это, мы пересекаем Record<P, T[P]>
с типом, который необязательно содержит остальные свойства (Exclude<keyof T, P>
), но заставляет их всех быть undefined
.