TypeScript: выбранные свойства необязательны с Partial & Omit
Когда вы используете утилиту Partial
для типа, она делает все свойства типа необязательными. Давайте посмотрим, как мы можем скомпоновать эту утилиту с другой Omit
, чтобы сделать необязательными только определенные свойства этого типа. Наконец, мы создадим служебный тип, используя дженерики TypeScript, чтобы применить это к любому типу, который мы захотим.
Сделать все свойства типа необязательными
Утилиты Partial
и Omit
хорошо известны в области TypeScript, потому что они очень удобны, чтобы быстро применить их к типу и получить, по сути, новый тип. Partial<T>
преобразует все ключи типа в необязательные. Если, например, у вас есть этот тип:
interface Band {
lead: string
guitar: string
bass: string
drums: string
keyboard: string
}
все свойства необходимы, когда что-то имеет этот тип. Если вы хотите назначить объект следующим образом:
const FooFighters: Band = {
lead: 'Dave Grohl',
guitar: 'Pat Smears',
bass: 'Nate Mendel',
drums: 'Taylor Williams',
}
он потерпит неудачу, потому что в нем отсутствует keyboard
свойство. Теперь мы можем пометить это свойство как необязательное, и TypeScript легко позволяет нам это сделать. Самый простой способ сделать это — добавить вопросительный знак к его имени. В этом случае, если возможно отредактировать исходный тип, вы можете перейти к нему и изменить его следующим образом:
interface Band {
lead: string
guitar: string
bass: string
drums: string
keyboard?: string
}
Большой. Теперь предыдущее назначение будет выполнено успешно, потому что keyboard
оно больше не требуется. Но что, если по какой-либо причине невозможно изменить исходный тип? Что вы можете быстро сделать в этих случаях, так это применить Partial
к этому типу:
type NotAllTheBand = Partial
const FooFighters: NotAllTheBand = {
lead: 'Dave Grohl',
guitar: 'Pat Smears',
bass: 'Nate Mendel',
drums: 'Taylor Williams',
}
Это останавливает применение всех свойств, делая их необязательными. Возможно, иногда нам это нужно, но что, если мы просто хотим сделать только одно свойство необязательным и избежать изменения исходного типа?
Удаление свойств из типа
Прежде чем мы перейдем к ней и посмотрим, как она работает, давайте посмотрим еще одну утилиту, которая поможет нам в этом: Omit
. Этот служебный тип удаляет свойства из типа, к которому он применяется. Пока Partial
не принимает других аргументов, кроме типа, Omit
принимает тип плюс свойства, которые вы хотите удалить: Omit<T, K>
.
Это не Partial
то, что делает их необязательными: Omit
полностью вычеркнет их из типа. Когда вы используете Partial<Band>
это, как если бы вы сделали это:
interface Band {
lead?: string
guitar?: string
bass?: string
drums?: string
keyboard?: string
}
Однако, когда вы используете Omit<Band, 'keyboard'>
, это как если бы вы сделали это:
interface Band {
lead: string
guitar: string
bass: string
drums: string
}
Теперь это начинает иметь смысл, верно? Можете ли вы представить, что мы могли бы сделать в TypeScript, если бы у нас был тип, у которого все свойства исходного набора были заданы как необязательные, и другой тип, у которого есть только те свойства, которые нам нужны? Если бы только был способ… пересекать эти типы, верно?
Пересекающиеся типы
Да, в TypeScript есть способ использовать амперсанд &
, оператор пересечения типов. Этот оператор, учитывая два типа, создает новый со свойствами, принадлежащими обоим типам:
interface SomeType {
propA: string
propB: number
}
interface AnotherType {
propC: boolean
propD: Array
}
type IntersectedType = SomeType & AnotherType
К настоящему времени вы, вероятно, поняли: мы собираемся пересечь партиализированный тип и тип, у которого мы удалили некоторые свойства.
Сделать выбранные свойства типа необязательными
Попробуем разобраться. При изучении TypeScript важно рационализировать то, что происходит с нашими типами.
Мы собираемся получить тип, все свойства которого установлены как необязательные, благодаря Partial
примененному к нему. Далее мы собираемся пересечь его с типом, свойства которого были удалены с помощью Omit
. Давайте посмотрим пример:
interface SomeType {
propA: string
propB: number
}
type OptionalPropB = Partial & Omit
Теперь у нас есть новый тип без изменения исходного типа, который выглядит следующим образом:
type OptionalPropB = {
propA: string
propB?: string
}
Это происходит потому, что мы берем свойства propA?
и propB?
из типа, созданного с помощью, Partial
и пересекаем их с propA
типом, созданным с помощью Omit
.
Общие утилиты TypeScript
Хорошо, теперь наш новый тип работает, и мы можем помечать только выбранные свойства как необязательные, не изменяя исходный тип. Однако эта линия
type OptionalPropB = Partial & Omit
Слишком многословно, довольно уродливо и утомительно писать каждый раз. Что еще более важно, он не будет работать для другого типа, нам придется каждый раз писать его заново. Можем ли мы сделать его короче и красивее, чтобы он работал для любого типа? Да, TypeScript позволяет нам использовать здесь дженерики и создавать собственный служебный тип:
type Optional = Partial & Omit
И теперь у нас есть собственный служебный тип TypeScript, реализованный с помощью универсальных шаблонов, которые мы можем использовать, чтобы сделать выбранные свойства типа необязательными для любого типа. Мы можем использовать это так:
type TypeWithOptionalProp = Optional
Вы заметите, что сигнатура похожа на Omit: эта утилита принимает тип для работы и свойства, которые будут сделаны необязательными. Если у вас их больше, вы можете использовать оператор объединения:
type TypeWithOptionalProps = Optional
Заключительные слова
Теперь вы можете сохранить этот универсальный тип утилиты в файле и экспортировать его, чтобы использовать во всем приложении. Вот ссылка на игровую площадку TypeScript, куда я добавил код из этой статьи и прокомментировал его, чтобы вы могли видеть, что происходит, глядя на Partial
, Omit
и наш общий тип утилиты.
Мир дженериков и утилит в TypeScript просто фантастический. Займитесь этим и начните экспериментировать!