TS - Pick и Omit

Утилита Pick Link to heading

interface Bulochka {
    price: number;
    weight: number;
    lenght: number;
    brand: string;
}

type PickedBulochka = Pick<Bulochka, 'price' | 'brand'>;

const bulochka: PickedBulochka = {
    price: 12,
    brand: 'white'
}

Утилита Pick включает из интерфейса Bulochka пары ключ-значение по указанным ключам, в нашем случае - price и brand. В итоге тип PickedBulochka будет содержать только такой набор:

type PickedBulochka = {
    price: number;
    brand: string;
}

Реальный пример из практики: Link to heading

private readonly _withoutAccount: Pick<Account, 'displayedName'> & { id: string } = {
    id: 'without',
    displayedName: 'Здесь нет моего договора'
};

… здесь Pick обращается к свойству displayedName класса Account; и в качестве свойства класса выступает геттер displayedName:

export class Account implements IAccountExtended {
    fullName: string;
    id: number;
    alias: string;
    addressFull: accounts.IAccountAddress = { identifier: false, value: '' };
    ...

    public get displayedName(): string {
        return `${this.alias || this.fullName || this.service.provider.name} (${this.tenancy.register})`;
    }

    constructor(source: accounts.IAccount, provider: Provider) {
        Object.assign(this, source);
        this.service.provider = provider;
    }

}

Утилита Omit Link to heading

interface Bulochka {
    price: number;
    weight: number;
    lenght: number;
    brand: string;
}

type PickedBulochka = Omit<Bulochka, 'price' | 'brand'>;

const bulochka: OmitedBulochka = {
    weight: 12,
    lenght: 20
}

Omit - прямая противоположность утилите Pick. Она исключает из интерфейса указанные ключи. В нашем случае в типе OmitedBulochka будут все ключи из интерфейса Bulochka, за исключением ключей price и brand:

type OmitedBulochka {
    weight: number;
    lenght: number;
}

Реальные примеры из практики Link to heading

Здесь видим интерфейс IUserToAccount, который расширяется другим интерфейсом IUser, при этом у интерфейса IUser выборочно забираются все поля, кроме поля fields. При этом обратим внимание на поле role - в нем также используется утилита Omit, при помощи которой поле role получает все поля из интерфейса IRole, кроме поля operations:

interface IUserToAccount extends Omit<users.IUser, 'fields'> {
    role: Omit<IRole, 'operations'>;
}

Еще один наглядный пример:

type IApiModal = Omit<IModal, 'pictures' | 'attachments'> & {
    pictures?: IApiPictureCollectionItem[];
    attachments: ApiAttachment[];
};

… здесь при помощи утилиты Omit из интерфейса IModal забираются все свойства, кроме pictures и attachments.

Далее видим еще один интересный инструмент TS - пересечение Intersection, обозначаемое символом &. При помощи Interseption в тип IApiModal добавляются еще два поля - pictures и attachments, взятые из литерала объекта.

Если сказать точнее - поля pictures и attachments - попадают в тип IApiModal с новыми типами, нежели какие у них были в интерфейсе IModal.

Можно сделать красивее:

type Modify<T, R> = Omit<T, keyof R> & R;

type ApiModal = Modify<IModal, {
    pictures?: IApiPictureCollectionItem[];
    attachments: ApiAttachment[];
};

Ссылки Link to heading