Valores padrão da interface de texto datilografado


137

Eu tenho a seguinte interface no TypeScript:

interface IX {
    a: string,
    b: any,
    c: AnotherType
}

Declaro uma variável desse tipo e inicializo todas as propriedades

let x: IX = {
    a: 'abc',
    b: null,
    c: null
}

Então eu atribuo valores reais a eles em uma função init mais tarde

x.a = 'xyz'
x.b = 123
x.c = new AnotherType()

Mas não gosto de especificar um monte de valores nulos padrão para cada propriedade ao declarar o objeto quando eles serão definidos posteriormente como valores reais. Posso dizer à interface para usar como padrão as propriedades que não forneço como nulas? O que me deixaria fazer isso:

let x: IX = {
    a: 'abc'
}

sem obter um erro do compilador. Agora, isso me diz

TS2322: O tipo '{}' não pode ser atribuído ao tipo 'IX'. A propriedade 'b' está ausente no tipo '{}'.



3
Na IMO, a resposta stackoverflow.com/a/35074490/129196 não deve ser a abordagem a ser adotada. Se você pode ter um objeto em um estado sem ter todas as suas propriedades inicializadas e ainda serem válidas, declare essas propriedades como opcionais, conforme indicado por esta resposta: stackoverflow.com/a/43226857/129196 . Caso contrário, perderemos o único objetivo de usar o texto datilografado (para segurança do tipo).
Charles Prakash Dasari

Respostas:


96

Posso dizer à interface para usar como padrão as propriedades que não forneço como nulas? O que me deixaria fazer isso

Não. Mas, por padrão, eles são os undefinedque estão bem. Você pode usar o seguinte padrão, ou seja, ter uma declaração de tipo no ponto de criação:

let x: IX = {} as any;

x.a = 'xyz'
x.b = 123
x.c = new AnotherType()

Eu tenho esse e outros padrões documentados aqui: https://basarat.gitbook.io/typescript/main-1/lazyobjectliteralinitialization


Obrigado, sem definir monte de valores padrão Eu costumava ter incapaz de valor definido para a propriedade indefinido
Omar eu disse

42
O uso anyprejudica o objetivo do Script de tipo . Existem outras respostas sem essa desvantagem.
Jack Miller

Alguém poderia me ajudar sobre uma pergunta semelhante, mas usando genéricos. Aqui nesta pergunta
TAB

2
É estranho que o basarat siga o exemplo 'any' quando, no link fornecido, ele oferece uma opção muito melhor com 'let foo = {} como Foo;' ('Foo "sendo uma interface)
Neurothustra 28/02/19

1
O OP se deu ao trabalho de criar uma interface TypeScript e está pedindo soluções para o revestimento da caldeira. Sua solução é renunciar completamente ao Interfaces? Pode também sugerir que salta typescript também ...
Moss Palmer

98

Você não pode definir valores padrão em uma interface, mas pode realizar o que deseja fazer usando Propriedades Opcionais (compare o parágrafo # 3):

https://www.typescriptlang.org/docs/handbook/interfaces.html

Simplesmente mude a interface para:

interface IX {
    a: string,
    b?: any,
    c?: AnotherType
}

Você pode então fazer:

let x: IX = {
    a: 'abc'
}

E use sua função init para atribuir valores padrão x.be x.cse essas propriedades não estiverem definidas.


3
Na pergunta, foi solicitado que você inicializasse x.be x.ccom null. Ao escrever let x = {a: 'abc'}seguida, x.be x.csão undefined, por isso, esta resposta não satisfaz plenamente as exigências, embora seja uma solução rápida inteligente.
Benny Neugebauer

1
@BennyNeugebauer A resposta aceita tem a mesma falha. Esta é a melhor resposta
tel

1
se x é objecto com os valores predefinidos, em seguida, criando objecto última instância let a: IX = Object.assign({b:true}, x);irá causar b, c para ser opcional também na instância de objecto, que não pode ser desejado
Luckylooke

As propriedades não devem ser marcadas como "opcionais" para conveniência dos usuários da interface. A resposta é "não", você não pode "dizer à interface para fornecer padrões", mas pode fornecer um método de fábrica para inicializar uma instância de uma interface.
Rick O'Shea

34

Enquanto a resposta do @ Timar funciona perfeitamente para nullos valores padrão (o que foi solicitado), aqui está outra solução fácil que permite outros valores padrão: Defina uma interface de opção e uma constante correspondente contendo os padrões; no construtor, use o operador spread para definir a optionsvariável de membro

interface IXOptions {
    a?: string,
    b?: any,
    c?: number
}

const XDefaults: IXOptions = {
    a: "default",
    b: null,
    c: 1
}

export class ClassX {
    private options: IXOptions;

    constructor(XOptions: IXOptions) {
        this.options = { ...XDefaults, ...XOptions };
    }

    public printOptions(): void {
        console.log(this.options.a);
        console.log(this.options.b);
        console.log(this.options.c);
    }
}

Agora você pode usar a classe assim:

const x = new ClassX({ a: "set" });
x.printOptions();

Resultado:

set
null
1

Isso não pode ser usado como uma solução padrão para todos os casos, mas definitivamente é uma solução criativa para alguns casos. 👍
abhay tripathi 23/06

what if, em vez de apenas registrar os valores, quero usá-los como const calcvalue = this.options.a * 1000; isso ainda provocaria alarme, pois pode ser potencialmente indefinido ..
Manish Rawat

Sim, você pode usar uma aula ou escrevê-la em outro idioma, nenhum dos quais responde à pergunta dele.
Rick O'Shea

22

Você pode implementar a interface com uma classe e, em seguida, lidar com a inicialização dos membros no construtor:

class IXClass implements IX {
    a: string;
    b: any;
    c: AnotherType;

    constructor(obj: IX);
    constructor(a: string, b: any, c: AnotherType);
    constructor() {
        if (arguments.length == 1) {
            this.a = arguments[0].a;
            this.b = arguments[0].b;
            this.c = arguments[0].c;
        } else {
            this.a = arguments[0];
            this.b = arguments[1];
            this.c = arguments[2];
        }
    }
}

Outra abordagem é usar uma função de fábrica:

function ixFactory(a: string, b: any, c: AnotherType): IX {
    return {
        a: a,
        b: b,
        c: c
    }
}

Então você pode simplesmente:

var ix: IX = null;
...

ix = new IXClass(...);
// or
ix = ixFactory(...);

4

Você pode usar o Partialtipo mapeado, conforme explicado na documentação: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html

No seu exemplo, você terá:

interface IX {
    a: string;
    b: any;
    c: AnotherType;
}

let x: Partial<IX> = {
    a: 'abc'
}

Se x é objeto com valores padrão, em seguida, criar objeto última instância let a: IX = Object.assign({b:true}, x);irá causar erroPartial<IX> cannot be assigned to IX
Luckylooke

Parcial altera o tipo. xnão implementa mais IX, mas uma parcial de IX. Partialé bom para locais onde todas as propriedades podem ser opcionais, por exemplo, com um ORM, onde você pode passar uma parte parcial de uma interface de objeto e atualizar apenas os campos definidos (em vez de undefinedqual é o campo de cada campo Partial). Para interfaces que possuem campos com valores padrão, é possível declarar literais de objeto implementando esse tipo de interface, sem precisar declarar os valores padrão, usando a let x: Partial<IX> = { /* non-default fields */ } as IXsintaxe.
Paul-Sebastian Manole

1

Eu tropecei nisso enquanto procurava uma maneira melhor do que aquilo que eu havia chegado. Depois de ler as respostas e experimentá-las, achei que valia a pena postar o que estava fazendo, pois as outras respostas não me pareciam sucintas. Era importante para mim ter que escrever apenas uma pequena quantidade de código toda vez que eu configurava uma nova interface. Eu decidi ...

Usando uma função deepCopy genérica customizada:

deepCopy = <T extends {}>(input: any): T => {
  return JSON.parse(JSON.stringify(input));
};

Defina sua interface

interface IX {
    a: string;
    b: any;
    c: AnotherType;
}

... e defina os padrões em uma const separada.

const XDef : IX = {
    a: '',
    b: null,
    c: null,
};

Então inicie assim:

let x : IX = deepCopy(XDef);

Isso é tudo o que é necessário ..

.. Contudo ..

Se você deseja inicializar personalizado qualquer elemento raiz, pode modificar a função deepCopy para aceitar valores padrão personalizados. A função se torna:

deepCopyAssign = <T extends {}>(input: any, rootOverwrites?: any): T => {
  return JSON.parse(JSON.stringify({ ...input, ...rootOverwrites }));
};

Que pode ser chamado assim:

let x : IX = deepCopyAssign(XDef, { a:'customInitValue' } );

Qualquer outra maneira preferida de cópia profunda funcionaria. Se apenas uma cópia superficial for necessária, o Object.assign será suficiente, renunciando à necessidade do utilitário deepCopyou deepCopyAssignfunção.

let x : IX = object.assign({}, XDef, { a:'customInitValue' });

Problemas conhecidos

  • Ela não será atribuída em profundidade nessa aparência, mas não é muito difícil modificar deepCopyAssignpara iterar e verificar os tipos antes da atribuição.
  • Funções e referências serão perdidas pelo processo de análise / stringify. Não preciso disso para minha tarefa e nem o OP.
  • Os valores personalizados do init não são sugeridos pelo IDE ou pelo tipo verificado quando executado.

0

Minha solução:

Eu criei o wrapper sobre Object.assign para corrigir problemas de digitação.

export function assign<T>(...args: T[] | Partial<T>[]): T {
  return Object.assign.apply(Object, [{}, ...args]);
}

Uso:

env.base.ts

export interface EnvironmentValues {
export interface EnvironmentValues {
  isBrowser: boolean;
  apiURL: string;
}

export const enviromentBaseValues: Partial<EnvironmentValues> = {
  isBrowser: typeof window !== 'undefined',
};

export default enviromentBaseValues;

env.dev.ts

import { EnvironmentValues, enviromentBaseValues } from './env.base';
import { assign } from '../utilities';

export const enviromentDevValues: EnvironmentValues = assign<EnvironmentValues>(
  {
    apiURL: '/api',
  },
  enviromentBaseValues
);

export default enviromentDevValues;
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.