Ustaw wszystkie właściwości w interfejsie Typescript jako opcjonalne


104

Mam interfejs w mojej aplikacji:

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

to jest część interfejsu posta:

interface Post {
  asset: Asset;
}

Mam również interfejs przeznaczony do wersji roboczej, w którym obiekt aktywów może być tylko częściowo zbudowany

interface PostDraft {
  asset: Asset;
}

Chcę zezwolić PostDraftobiektowi na posiadanie częściowego obiektu aktywów, jednocześnie sprawdzając typy właściwości, które tam są (więc nie chcę go po prostu zamieniać any).

Zasadniczo chcę mieć możliwość generowania następujących elementów:

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

bez całkowitego ponownego definiowania Assetinterfejsu. Czy jest na to sposób? Jeśli nie, jaki byłby mądry sposób ułożenia moich typów w tej sytuacji?


Dzisiaj musisz ręcznie utworzyć ten drugi interfejs, chociaż może to się zmienić w najbliższej przyszłości: sprawdź problem z typami częściowymi w repozytorium ts, jeśli jesteś zainteresowany.
— Alex Guerra

Odpowiedzi:


220

Nie jest to możliwe w TypeScript <2.1 bez tworzenia dodatkowego interfejsu z opcjonalnymi właściwościami; jest to jednak możliwe przy użyciu mapowanych typów w języku TypeScript 2.1+.

Aby to zrobić, użyj Partial<T>typu, który domyślnie udostępnia TypeScript.

interface PostDraft {
    asset: Partial<Asset>;
}

Teraz wszystkie włączone właściwości assetsą opcjonalne, co pozwoli ci wykonać następujące czynności:

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

O Partial<T>

Partial<T>jest zdefiniowany jako typ mapowany, co powoduje, że każda właściwość w podanym typie jest opcjonalna (przy użyciu ?tokenu).

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

Przeczytaj więcej o mapowanych typach tutaj oraz w podręczniku .

Głębokie Częściowe

Jeśli potrzebujesz częściowej implementacji, która działa rekurencyjnie na obiektach, możesz użyć następującego typu w TS 4.1+:

type DeepPartial<T> = {
    [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

@david czy można pójść w drugą stronę? Aby zaznaczyć pola, które wcześniej były częściowe jako wymagane? A także czy można to zrobić dla każdego pola?
— Tony

1
@ Tony tak, będzie to możliwe z typami warunkowymi w TS 2.8 (patrz Required<T>).
— David Sherret

2
omg, to takie czyste rozwiązanie. Szkoda, że ​​nie wiedziałem o tym trochę wcześniej
— CiriousJoker,

3
Nadal byłem zdumiony myślą, która trafiła do TS ... od Microsoftu. Chciałbym, aby ta sama intencjonalność została wprowadzona do rozwoju ich przeglądarki internetowej (-_-)
— GreaterKing

5

Właściwości w interfejsie są opcjonalne albo nie, nie można użyć tego samego interfejsu raz jako opcjonalnego i raz w razie potrzeby.

Co możesz zrobić, to mieć interfejs z opcjonalnymi właściwościami dla AssetDraftklasy, a następnie klasę z obowiązkowymi właściwościami dla 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;
    }
}

Domyślne wartości tutaj to statyczne elementy członkowskie, ale możesz je uzyskać w inny sposób lub zgłosić błąd, jeśli ich brakuje.

Uważam, że ten sposób jest bardzo wygodny podczas pracy z jsonami otrzymanymi z serwera (lub czymś podobnym), interfejsy reprezentują dane json, a klasy są rzeczywistymi modelami, które są konstruowane przy użyciu jsonów jako wartości początkowych.


Tak, możesz ... jak stwierdzono w odpowiedzi Davida Sherreta ... musiałbyś użyć zmiennej typu ogólnego, aby przekazać swój interfejs jako Partial <T> ..T będący twoim interfejsem.
— GreaterKing

@greaterKing teraz jest to możliwe i łatwe
— Nitzan Tomer

1

Jeśli chcę mieć wyraźny AssetDraftinterfejs, użyłbym kombinacji extendsi Partial:

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

interface AssetDraft extends Partial<Asset> {}

0

Oprócz Davida Sherreta odpowiedz tylko w kilku wierszach z mojej strony, jak można to zaimplementować bezpośrednio bez Partial<T>pisania, aby lepiej zrozumieć temat.

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

-1

Co powiesz na wymuszone rzucenie pustego przedmiotu np

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

Jeśli naprawdę tego potrzebujesz, zawsze możesz wygenerować interfejs d.ts z szablonu, który zawiera zarówno opcjonalne, jak i wpisane właściwości.

Jak zauważył Nitzan, albo właściwości interfejsu Typescript są opcjonalne, albo nie

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.