Hacer que todas las propiedades dentro de una interfaz de TypeScript sean opcionales

96

Tengo una interfaz en mi aplicación:

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

que es parte de una interfaz de publicación:

interface Post {
  asset: Asset;
}

También tengo una interfaz que es para un borrador de publicación, donde el objeto de activo solo puede construirse parcialmente

interface PostDraft {
  asset: Asset;
}

Quiero permitir que un PostDraftobjeto tenga un objeto de activo parcial mientras sigo verificando los tipos en las propiedades que están allí (por lo que no quiero simplemente intercambiarlo any).

Básicamente, quiero una forma de poder generar lo siguiente:

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

sin redefinir completamente la Assetinterfaz. ¿Hay alguna forma de hacer esto? Si no es así, ¿cuál sería la forma inteligente de organizar mis tipos en esta situación?

jkjustjoshing
fuente
Hoy necesita crear esa segunda interfaz manualmente, aunque esto podría cambiar en un futuro cercano: consulte el problema de tipos parciales en el repositorio ts si está interesado.
Alex Guerra

Respuestas:

208

Esto no es posible en TypeScript <2.1 sin crear una interfaz adicional con propiedades opcionales; sin embargo, esto es posible mediante el uso de tipos asignados en TypeScript 2.1+.

Para hacer esto, use el Partial<T>tipo que TypeScript proporciona por defecto.

interface PostDraft {
    asset: Partial<Asset>;
}

Ahora todas las propiedades de assetson opcionales, lo que le permitirá hacer lo siguiente:

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

Acerca de Partial<T>

Partial<T>se define como un tipo mapeado que hace que todas las propiedades en el tipo proporcionado sean opcionales (usando el ?token).

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

Lea más sobre los tipos asignados aquí y en el manual .

David Sherret
fuente
@david ¿es posible ir al revés? ¿Para marcar campos que antes eran parciales como obligatorios? ¿Y también es posible hacerlo por campo?
Tony
1
@Tony sí, será posible con tipos condicionales en TS 2.8 (ver Required<T>).
David Sherret
2
Dios mío, esta es una solución tan limpia. Ojalá supiera esto un poco antes
CiriousJoker
3
Continuó sorprendido por la idea que entró en TS ... de Microsoft nada menos. Desearía que se
aplicara
4

Las propiedades de la interfaz son opcionales, no lo son, no puede usar la misma interfaz una vez como opcional y una vez como debe.

Lo que puede hacer es tener una interfaz con propiedades opcionales para el AssetDrafty luego una clase con propiedades obligatorias para 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;
    }
}

Los valores predeterminados aquí son miembros estáticos, pero puede obtenerlos de otras formas o lanzar un error en caso de que falten.

Me parece muy cómodo de esta manera cuando trabajo con jsons que se reciben del servidor (o algo similar), las interfaces representan los datos de json y las clases son los modelos reales que se construyen utilizando los jsons como valores iniciales.

Nitzan Tomer
fuente
Sí, puede ... como se indica en la respuesta de David Sherret ... tendría que usar una variable de tipo genérico para pasar su interfaz como Partial <T> ..T es su interfaz.
GreaterKing
@greaterKing ahora es posible y fácil
Nitzan Tomer
1

Si quiero tener una AssetDraftinterfaz explícita , usaría una combinación de extendsy Partial:

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

interface AssetDraft extends Partial<Asset> {}
dhilt
fuente
0

Además de la respuesta de David Sherret, solo unas líneas de mi lado, cómo se puede implementar directamente sin tipografíaPartial<T> para una mejor comprensión del tema.

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
  }
};
WebBrother
fuente
-1

¿Qué tal forzar el lanzamiento de un objeto vacío?

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

Si realmente lo necesita, siempre puede generar una interfaz d.ts a partir de una plantilla que haga las propiedades opcionales y escritas.

Como señaló Nitzan, las propiedades de la interfaz de TypeScript son opcionales o no

user3893988
fuente