Tornar opcionais todas as propriedades de uma interface Typescript

103

Eu tenho uma interface em meu aplicativo:

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

que faz parte de uma interface de postagem:

interface Post {
  asset: Asset;
}

Eu também tenho uma interface que é para um pós-rascunho, onde o objeto de ativo pode ser apenas parcialmente construído

interface PostDraft {
  asset: Asset;
}

Quero permitir que um PostDraftobjeto tenha um objeto de ativo parcial enquanto ainda verifico os tipos nas propriedades que estão lá (portanto, não quero apenas trocá-lo por any).

Eu basicamente quero uma maneira de gerar o seguinte:

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

sem redefinir totalmente a Assetinterface. Existe uma maneira de fazer isso? Se não, qual seria a maneira inteligente de organizar meus tipos nessa situação?

jkjustjoshing
fonte
Hoje você precisa fazer essa segunda interface manualmente, embora isso possa mudar em um futuro próximo: verifique o problema de tipos parciais no repositório ts se estiver interessado.
Alex Guerra

Respostas:

220

Isso não é possível no TypeScript <2.1 sem criar uma interface adicional com propriedades opcionais; no entanto, isso é possível usando tipos mapeados em TypeScript 2.1+.

Para fazer isso, use o Partial<T>tipo que o TypeScript fornece por padrão.

interface PostDraft {
    asset: Partial<Asset>;
}

Agora, todas as propriedades ativadas assetsão opcionais, o que permitirá que você faça o seguinte:

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

Sobre Partial<T>

Partial<T>é definido como um tipo mapeado que torna cada propriedade no tipo fornecido opcional (usando o ?token).

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

Leia mais sobre os tipos mapeados aqui e no manual .

Parcial Profundo

Se você quiser uma implementação parcial que funcione recursivamente em objetos, poderá usar o seguinte tipo no TS 4.1+:

type DeepPartial<T> = {
    [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
David Sherret
fonte
@david é possível ir por outro caminho? Para marcar campos que anteriormente eram parciais como obrigatórios? E também é possível fazer isso em uma base por campo?
Tony
1
@Tony sim, será possível com tipos condicionais no TS 2.8 (consulte RecursosRequired<T> ).
David Sherret
2
omg, esta é uma solução tão limpa. Gostaria de saber disso um pouco antes
CiriousJoker,
3
Continuou a se surpreender com o pensamento que veio do TS ... da Microsoft, nada menos. Gostaria que a mesma intencionalidade fosse
aplicada ao
5

As propriedades na interface são opcionais ou não, você não pode usar a mesma interface uma vez como opcional e uma vez como deve.

O que você pode fazer é ter uma interface com propriedades opcionais para o AssetDrafte, em seguida, uma classe com propriedades obrigatórias 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;
    }
}

Os valores padrão aqui são membros estáticos, mas você pode obtê-los de outras maneiras ou lançar um erro caso eles estejam ausentes.

Acho essa maneira muito confortável ao trabalhar com jsons que são recebidos do servidor (ou algo semelhante), as interfaces representam os dados json e as classes são os modelos reais que são construídos usando jsons como valores iniciais.

Nitzan Tomer
fonte
Sim, você pode .. como afirmado na resposta de David Sherret ... você teria que usar a variável de tipo genérico para passar sua interface como Parcial <T> .. T sendo sua interface.
Rei
@greaterKing agora é possível e fácil
Nitzan Tomer
1

Se eu quiser uma AssetDraftinterface explícita , usaria uma combinação de extendse Partial:

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

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

Além de David Sherret responder apenas algumas linhas de minha parte como pode ser implementado diretamente sem Partial<T>digitar para melhor compreensão do assunto.

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
fonte
-1

Que tal forçar o lançamento de um objeto vazio, por exemplo

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

Se você realmente precisa disso, você sempre pode gerar uma interface d.ts a partir de um modelo que torna as propriedades opcionais e digitadas.

Como Nitzan apontou, as propriedades da interface Typescript são opcionais ou não

user3893988
fonte