Valores predeterminados de la interfaz de mecanografía


137

Tengo la siguiente interfaz en TypeScript:

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

Declaro una variable de ese tipo e inicializo todas las propiedades

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

Luego les asigno valores reales en una función init más tarde

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

Pero no me gusta tener que especificar un grupo de valores nulos predeterminados para cada propiedad al declarar el objeto cuando se establecerán más tarde en valores reales. ¿Puedo decirle a la interfaz que predetermina las propiedades que no proporciono a nulo? ¿Qué me dejaría hacer esto?

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

sin obtener un error del compilador. Ahora me dice

TS2322: El tipo '{}' no se puede asignar al tipo 'IX'. Falta la propiedad 'b' en el tipo '{}'.



3
En mi opinión, la respuesta stackoverflow.com/a/35074490/129196 no debería ser el enfoque a seguir. Si puede tener un objeto en un estado sin tener todas sus propiedades inicializadas y seguir siendo válidas, entonces debe declarar esas propiedades como opcionales como se indica en esta respuesta: stackoverflow.com/a/43226857/129196 . De lo contrario, perderemos el único propósito de utilizar el mecanografiado (por razones de seguridad).
— Charles Prakash Dasari

Respuestas:


97

¿Puedo decirle a la interfaz que predetermina las propiedades que no proporciono a nulo? ¿Qué me dejaría hacer esto?

No. Pero por defecto son lo undefinedque en general está bien. Puede usar el siguiente patrón, es decir, tener una aserción de tipo en el punto de creación:

let x: IX = {} as any;

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

Tengo este y otros patrones documentados aquí: https://basarat.gitbook.io/typescript/main-1/lazyobjectliteralinitialization


Gracias, sin definir un montón de valores predeterminados que no podía establecer el valor de una propiedad indefinida
— Omar Isaid

42
El uso anysocava el propósito de Type Script. Hay otras respuestas sin este inconveniente.
— Jack Miller

¿Alguien podría ayudarme con preguntas similares pero con genéricos? Aquí en esta pregunta
— TAB

2
Es extraño que basarat vaya con el ejemplo 'any' cuando, en el enlace proporcionado, ofrece una opción mucho mejor con 'let foo = {} como Foo;' ('Foo "es una interfaz)
— Neurothustra

1
El OP se ha tomado la molestia de crear una interfaz TypeScript y está pidiendo soluciones para el revestimiento de la caldera. ¿Su solución es renunciar a las interfaces por completo? También podría sugerir que omita TypeScript también ...
— Moss Palmer

98

No puede establecer valores predeterminados en una interfaz, pero puede lograr lo que desea hacer utilizando Propiedades opcionales (compare el párrafo 3):

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

Simplemente cambie la interfaz a:

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

Entonces puedes hacer:

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

Y use su función init para asignar valores predeterminados x.by x.csi esas propiedades no están establecidas.


3
En la pregunta se le pidió inicializar x.by x.ccon null. Al escribir let x = {a: 'abc'}entonces x.by x.cson undefined, por lo que esta respuesta no cumple completamente los requisitos, aunque es una solución rápida inteligente.
— Benny Neugebauer

1
@BennyNeugebauer La respuesta aceptada tiene el mismo defecto. Esta es la mejor respuesta
— tel

1
si x es objeto con valores por defecto, entonces la creación de objeto última instancia let a: IX = Object.assign({b:true}, x);provocará b, c sea opcional también en instancia de objeto, que puede no ser deseado
— Luckylooke

Las propiedades no deben marcarse como "opcionales" para conveniencia de quienes usan la interfaz. La respuesta es "no", no puede "indicarle a la interfaz que proporcione los valores predeterminados", pero puede proporcionar un método de fábrica para inicializar una instancia de una interfaz.
— Rick O'Shea

34

Si bien la respuesta de @ Timar funciona perfectamente para nulllos valores predeterminados (lo que se solicitó), aquí hay otra solución fácil que permite otros valores predeterminados: definir una interfaz de opción, así como una constante correspondiente que contenga los valores predeterminados; en el constructor use el operador de propagación para establecer la optionsvariable miembro

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

Ahora puedes usar la clase así:

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

Salida:

set
null
1

Esto no puede usarse como una solución predeterminada para cada caso, pero definitivamente es una solución creativa para algunos casos. 👍
— abhay tripathi

¿Qué pasa si en lugar de simplemente registrar los valores que quiero usar como const calcvalue = this.options.a * 1000; esto aún despertaría la alarma, ya que puede estar potencialmente indefinido ..
— Manish Rawat

Sí, puede usar una clase o escribirla en otro idioma, ninguno de los cuales responde a su pregunta.
— Rick O'Shea

22

Puede implementar la interfaz con una clase, luego puede tratar de inicializar los miembros en el constructor:

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

Otro enfoque es usar una función de fábrica:

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

Entonces puedes simplemente:

var ix: IX = null;
...

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

4

Puede usar el Partialtipo asignado como se explica en la documentación: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html

En tu ejemplo, tendrás:

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

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

si x es un objeto con valores predeterminados, crear un objeto de instancia final let a: IX = Object.assign({b:true}, x);causará un errorPartial<IX> cannot be assigned to IX
— Luckylooke

Parcial cambia el tipo. xya no se implementa IX, sino un parcial de IX. Partiales bueno para lugares donde cada propiedad podría ser opcional, por ejemplo, con un ORM, donde puede pasar una interfaz parcial de un objeto y actualizar solo los campos que están definidos (en lugar de undefinedlo que Partialpuede ser cada campo de a ). Para las interfaces que tienen campos con valores predeterminados, puede declarar literales de objeto que implementen ese tipo de interfaces, sin tener que declarar los valores predeterminados, utilizando la let x: Partial<IX> = { /* non-default fields */ } as IXsintaxis.
— Paul-Sebastian Manole

1

Me topé con esto mientras buscaba una forma mejor de lo que había llegado. Después de leer las respuestas y probarlas, pensé que valía la pena publicar lo que estaba haciendo, ya que las otras respuestas no me parecieron tan breves. Para mí era importante tener que escribir una pequeña cantidad de código cada vez que configuraba una nueva interfaz. Me decidí por ...

Usando una función personalizada de deepCopy genérica:

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

Define tu interfaz

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

... y define los valores predeterminados en una const separada.

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

Entonces inicie así:

let x : IX = deepCopy(XDef);

Eso es todo lo que se necesita ...

.. sin embargo ..

Si desea inicializar cualquier elemento raíz , puede modificar la función deepCopy para aceptar valores predeterminados personalizados. La función se convierte en:

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

Que luego se puede llamar así:

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

Cualquier otra forma preferida de copia profunda funcionaría. Si solo se necesita una copia superficial, Object.assign sería suficiente, renunciando a la necesidad de la utilidad deepCopyo deepCopyAssignfunción.

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

Problemas conocidos

  • No se asignará en profundidad de esta manera, pero no es demasiado difícil de modificar deepCopyAssignpara iterar y verificar los tipos antes de asignar.
  • El proceso de análisis / stringify perderá funciones y referencias. No los necesito para mi tarea y tampoco el OP.
  • Los valores de inicio personalizados no son insinuados por el IDE o el tipo verificado cuando se ejecuta.

0

Mi solución:

He creado un contenedor sobre Object.assign para solucionar problemas de escritura.

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;
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.