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 de inicio más tarde

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

Pero no me gusta tener que especificar un montón 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 por defecto las propiedades que no proporciono sean nulas? ¿Qué me permitiría hacer esto?

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

Sin obtener un error del compilador. Ahora mismo me dice

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

225
d512 29 ene. 2016 a las 02:47

8 respuestas

La mejor respuesta

¿Puedo decirle a la interfaz que por defecto las propiedades que no proporciono sean nulas? ¿Qué me dejaría hacer esto?

No. Pero por defecto son undefined, lo que en su mayoría está bien. Puede usar el siguiente patrón, es decir, tener una afirmació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

107
zarkone 21 jul. 2020 a las 06:07

Podría usar dos configuraciones separadas. Uno como entrada con propiedades opcionales (que tendrán valores predeterminados) y otro con solo las propiedades requeridas. Esto se puede hacer conveniente con & y Required:

interface DefaultedFuncConfig {
  b?: boolean;
}

interface MandatoryFuncConfig {
  a: boolean;
}

export type FuncConfig = MandatoryFuncConfig & DefaultedFuncConfig;
 
export const func = (config: FuncConfig): Required<FuncConfig> => ({
  b: true,
  ...config
});

// will compile
func({ a: true });
func({ a: true, b: true });

// will error
func({ b: true });
func({});
0
Steven Vachon 20 ago. 2020 a las 15:07

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;
0
Luckylooke 2 nov. 2019 a las 10:36

Me topé con esto mientras buscaba una manera 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 deepCopy genérica personalizada:

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

Define tu interfaz

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

... y defina los valores predeterminados en una const.

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

Entonces init así:

let x : IX = deepCopy(XDef);

Eso es todo lo que se necesita ...

.. sin embargo ...

Si desea inicializar de forma personalizada 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í en su lugar:

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

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

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

Problemas conocidos

  • No se asignará en profundidad de esta forma, pero no es demasiado difícil modificar deepCopyAssign para iterar y verificar tipos antes de asignar.
  • Las funciones y referencias se perderán con el proceso de análisis / secuenciación. No los necesito para mi tarea y tampoco el OP.
  • Los valores de inicio personalizados no son sugeridos por el IDE ni se verifica el tipo cuando se ejecuta.
1
Moss Palmer 19 may. 2019 a las 06:41

Puede utilizar el tipo mapeado Partial como se explica en la documentación: https://www.typescriptlang.org/docs/handbook /release-notes/typescript-2-1.html

En su ejemplo, tendrá:

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

let x: Partial<IX> = {
    a: 'abc'
}
6
F. Bauer 5 jul. 2019 a las 22:39

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 utilizar 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(...);
24
Nitzan Tomer 29 ene. 2016 a las 01:27

Si bien la respuesta de @ Timar funciona perfectamente para los valores predeterminados de null (lo que se solicitó), aquí hay otra solución fácil que permite otros valores predeterminados: Defina 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 variable miembro options

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
49
oluckyman 4 mar. 2019 a las 18:58

No puede establecer valores predeterminados en una interfaz, pero puede lograr lo que desea hacer usando 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 a x.b y x.c si esas propiedades no están configuradas.

120
Timar 5 abr. 2017 a las 09:16