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;
}

Это менее универсально, чем решение Адриана, но немного проще, когда нам не нужна сложность.