TypeScript: удалить ключ из типа/вычитания
Я хочу определить общий тип ExcludeCart<T>
, который по существу T
, но с указанным ключом (в моем случае, cart
) удален. Так, например, ExcludeCart<{foo: number, bar: string, cart: number}>
будет {foo: number, bar: string}
. Есть ли способ сделать это в TypeScript?
Вот почему я хочу это сделать, если я подкражу неправильное дерево: я конвертирую существующую кодовую базу JavaScript в TypeScript, которая содержит функцию декоратора под названием cartify
, которая принимает класс компонента React Inner
и возвращает другой класс компонентов Wrapper
.
Inner
должен принимать cart
prop, и ноль или более других реквизитов. Wrapper
принимает cartClient
prop (который используется для генерации cart
prop для перехода к Inner
), и любая поддержка, которую Inner
принимает, кроме cart
.
Другими словами, как только я могу определить, как определить ExcludeCart
, я хочу сделать это с ним:
function cartify<P extends {cart: any}>(Inner: ComponentClass<P>) : ComponentClass<ExcludeCart<P> & {cartClient: any}>
Ответы
Ответ 1
Хотя встроенного типа вычитания не существует, в настоящее время его можно взломать следующим образом:
type Sub0<
O extends string,
D extends string,
> = {[K in O]: (Record<D, never> & Record<string, K>)[K]}
type Sub<
O extends string,
D extends string,
// issue 16018
Foo extends Sub0<O, D> = Sub0<O, D>
> = Foo[O]
type Omit<
O,
D extends string,
// issue 16018
Foo extends Sub0<keyof O, D> = Sub0<keyof O, D>
> = Pick<O, Foo[keyof O]>
В этом случае вы бы сделали:
type ExcludeCart<T> = Omit<T, 'cart'>
С помощью TypeScript> = 2.6 вы можете упростить его до:
/**
* for literal unions
* @example Sub<'Y' | 'X', 'X'> // === 'Y'
*/
export type Sub<
O extends string,
D extends string
> = {[K in O]: (Record<D, never> & Record<string, K>)[K]}[O]
/**
* Remove the keys represented by the string union type D from the object type O.
*
* @example Omit<{a: number, b: string}, 'a'> // === {b: string}
* @example Omit<{a: number, b: string}, keyof {a: number}> // === {b: string}
*/
export type Omit<O, D extends string> = Pick<O, Sub<keyof O, D>>
проверить это на детской площадке
Ответ 2
Начиная с TypeScript 2.8 и введения Exclude
, теперь можно написать это следующим образом:
type Without<T, K> = {
[L in Exclude<keyof T, K>]: T[L]
};
Или альтернативно, и более кратко, как:
type Without<T, K> = Pick<T, Exclude<keyof T, K>>;
Теперь вы можете написать следующее:
type ExcludeCart<T> = Without<T, "cart">;
Ответ 3
Обновление: см. Адриан ответить выше для решения этого вопроса. Я оставил свой ответ здесь, хотя, поскольку он все еще содержит полезные ссылки.
Существуют различные старые запросы для этой функции (типы "outersection" , вычитания), но ни один из них не прогрессировал.
В последнее время с добавлением отображаемых типов я спросил об этом еще раз, и Андерс сказал, что пока не планируется создание оператора общего вычитания, может быть реализована более ограниченная версия, предположительно выглядящая как это предложение.
Я лично сталкивался с вами в подобных ситуациях при работе с React и, к сожалению, не смог найти подходящего решения. В простом случае вы можете уйти с чем-то вроде:
interface BaseProps {
foo: number;
bar: number;
}
interface Inner extends BaseProps {
cart: Cart;
}
interface Wrapper extends BaseProps {
cartClient: Client;
}
но я почти считаю это семантическим злоупотреблением ключевым словом extends
. И, конечно, если вы не управляете типом Inner
или BaseProps
, то это не сработает.
Ответ 4
So, for instance, ExcludeCart<{foo: number, bar: string, cart: number}> would be {foo: number, bar: string}
Вы можете использовать синтаксис Исключить, чтобы сделать это напрямую:
Exclude<{foo: number, bar: string, cart: number}, { cart: number}>
Ответ 5
есть еще один очень простой способ получить такой результат
При объединении типа в машинописном тексте тип "никогда" имеет более высокий приоритет ко всему.
Вы можете просто создать тип:
type noCart<T> = T & {cart : never}
Или без создания типа
function removeCart<T>(obj : T) : T & {cart : never} {
if("cart" in obj) {
delete (obj as T & {cart : any}).cart;
}
return <T & {cart : never}> obj;
}
Это менее универсально, чем решение Адриана, но немного проще, когда нам не нужна сложность.