Сделать все свойства в интерфейсе Typescript необязательным

У меня есть интерфейс в моем приложении:

interface Asset {
  id: string;
  internal_id: string;
  usage: number;
}

который является частью почтового интерфейса:

interface Post {
  asset: Asset;
}

У меня также есть интерфейс, который предназначен для черновика сообщения, где объект ресурса может быть только частично сконструирован

interface PostDraft {
  asset: Asset;
}

Я хочу разрешить объекту PostDraft иметь объект частичного объекта, все еще проверяя типы свойств, которые есть (поэтому я не хочу просто заменять его any).

В основном я хочу, чтобы можно было создать следующее:

interface AssetDraft {
  id?: string;
  internal_id?: string;
  usage?: number;
}

без полного переопределения интерфейса Asset. Есть ли способ сделать это? Если нет, то каким будет разумный способ упорядочить мои типы в этой ситуации?

Ответ 1

Это невозможно в TypeScript <2.1 без создания дополнительного интерфейса с дополнительными свойствами; однако это возможно при использовании Mapped Types в TypeScript 2. 1+.

Для этого используйте тип Partial<T> который TypeScript предоставляет по умолчанию.

interface PostDraft {
    asset: Partial<Asset>;
}

Теперь все свойства asset являются необязательными, что позволит вам сделать следующее:

const postDraft: PostDraft = {
    asset: {
        id: "some-id"
    }
};

О Partial<T>

Partial<T> определяется как сопоставленный тип, который делает каждое свойство необязательным (с помощью маркера ?).

type Partial<T> = {
    [P in keyof T]?: T[P];
};

Подробнее о отображаемых типах читайте здесь и в справочнике.

Ответ 2

Свойства в интерфейсе являются необязательными: нет, вы не можете использовать один и тот же интерфейс один раз как необязательный и один раз, как необходимо.

Что вы можете сделать, так это иметь интерфейс с необязательными свойствами для AssetDraft, а затем класс с обязательными свойствами для Asset:

interface AssetDraft {
    id?: string;
    internal_id?: string;
    usage?: number;
}

class Asset {
    static DEFAULT_ID = "id";
    static DEFAULT_INTERNAL_ID = "internalid";
    static DEFAULT_USAGE = 0;

    id: string;
    internal_id: string;
    usage: number;

    constructor(draft: AssetDraft) {
        this.id = draft.id || Asset.DEFAULT_ID;
        this.internal_id = draft.internal_id || Asset.DEFAULT_INTERNAL_ID;
        this.usage = draft.usage || Asset.DEFAULT_USAGE;
    }
}

Значения по умолчанию здесь являются статическими членами, но вы можете получить их другими способами или выбросить ошибку, если они отсутствуют.

Я нахожу этот способ очень удобным при работе с jsons, которые получены с сервера (или что-то подобное), интерфейсы представляют данные json, а классы - это фактические модели, которые построены с использованием jsons в качестве начальных значений.

Ответ 3

Как насчет принудительного бросания пустого объекта, например

const draft = <PostDraft>{}
draft.id = 123
draft.internal_id = 456
draft.usage = 789

Если вам это действительно нужно, вы всегда можете сгенерировать интерфейс d.ts из шаблона, который задает как необязательные, так и типизированные свойства.

Как отметил Ницан, либо свойства интерфейса Typescript являются необязательными, либо нет

Ответ 4

В дополнение к Дэвиду Шеррету ответьте лишь на несколько строк с моей стороны, как это можно реализовать напрямую без Partial<T> для лучшего понимания предмета.

interface IAsset {
  id: string;
  internal_id: string;
  usage: number;
}

interface IPost {
  asset: IAsset;
}

interface IPostDraft {
  asset: { [K in keyof IAsset]?: IAsset[K] };
}

const postDraft: IPostDraft = {
  asset: {
    usage: 123
  }
};