Valeurs par défaut de l'interface Typescript


137

J'ai l'interface suivante dans TypeScript:

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

Je déclare une variable de ce type et j'initialise toutes les propriétés

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

Ensuite, je leur attribue des valeurs réelles dans une fonction init plus tard

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

Mais je n'aime pas avoir à spécifier un tas de valeurs nulles par défaut pour chaque propriété lors de la déclaration de l'objet, alors qu'elles seront simplement définies plus tard sur des valeurs réelles. Puis-je dire à l'interface de définir par défaut les propriétés que je ne fournis pas à null? Qu'est-ce qui me permettrait de faire ceci:

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

sans obtenir une erreur du compilateur. En ce moment, ça me dit

TS2322: Le type '{}' n'est pas attribuable au type 'IX'. La propriété 'b' est manquante dans le type '{}'.


J'ai ajouté des documents pour vous: basarat.gitbooks.io/typescript/content/docs/tips/…
— basarat

3
OMI, la réponse stackoverflow.com/a/35074490/129196 ne devrait pas être l'approche à adopter. Si vous pouvez avoir un objet dans un état sans que toutes ses propriétés soient initialisées et toujours valides, vous devez déclarer ces propriétés comme facultatives, comme indiqué par cette réponse: stackoverflow.com/a/43226857/129196 . Sinon, nous perdrons le seul but de l'utilisation de dactylographié (pour la sécurité des types).
— Charles Prakash Dasari

Réponses:


98

Puis-je dire à l'interface de définir par défaut les propriétés que je ne fournis pas à null? Qu'est-ce qui me permettrait de faire ça

Non, mais par défaut, ils le sont, undefinedce qui est généralement très bien. Vous pouvez utiliser le modèle suivant, c'est-à-dire avoir une assertion de type au point de création:

let x: IX = {} as any;

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

J'ai ceci et d'autres modèles documentés ici: https://basarat.gitbook.io/typescript/main-1/lazyobjectliteralinitialization


Merci, sans définir un tas de valeurs par défaut, je
— n'avais

43
L'utilisation anysape l'objectif de Type Script. Il existe d'autres réponses sans cet inconvénient.
— Jack Miller

quelqu'un pourrait-il m'aider sur une question similaire mais en utilisant des génériques. Ici à cette question
— TAB

2
Bizarre que le basarat irait avec l'exemple 'any' quand, dans le lien fourni, il offre une bien meilleure option avec 'let foo = {} comme Foo;' («Foo» étant une interface)
— Neurothustra

1
L'OP s'est donné la peine de créer une interface TypeScript et demande des solutions pour le placage de la chaudière. Votre solution est de renoncer complètement aux interfaces? Peut aussi suggérer que saute TypeScript aussi ...
— Moss Palmer

98

Vous ne pouvez pas définir de valeurs par défaut dans une interface, mais vous pouvez accomplir ce que vous voulez faire en utilisant les propriétés facultatives (comparez le paragraphe 3):

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

Changez simplement l'interface en:

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

Vous pouvez alors faire:

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

Et utilisez votre fonction init pour attribuer des valeurs par défaut à x.bet x.csi ces propriétés ne sont pas définies.


3
Dans la question, il a été demandé d'initialiser x.bet x.cavec null. Lorsque vous écrivez let x = {a: 'abc'}alors x.bet x.csont undefined, cette réponse ne répond pas entièrement aux exigences, bien que ce soit une solution rapide et intelligente.
— Benny Neugebauer

1
@BennyNeugebauer La réponse acceptée présente le même défaut. C'est la meilleure réponse
— tel

1
si x est un objet avec des valeurs par défaut, la création d'un objet d'instance finale let a: IX = Object.assign({b:true}, x);rendra b, c facultatif également dans l'objet d'instance, ce qui peut ne pas être souhaité
— Luckylooke

Les propriétés ne doivent pas être marquées «facultatives» pour la commodité de ceux qui utilisent l'interface. La réponse est "non", vous ne pouvez pas "indiquer à l'interface de fournir les valeurs par défaut", mais vous pouvez fournir une méthode d'usine pour initialiser une instance d'une interface.
— Rick O'Shea

35

Alors que la réponse de @ Timar fonctionne parfaitement pour les nullvaleurs par défaut (ce qui a été demandé), voici une autre solution simple qui autorise d'autres valeurs par défaut: Définissez une interface d'option ainsi qu'une constante correspondante contenant les valeurs par défaut; dans le constructeur, utilisez l' opérateur spread pour définir la optionsvariable membre

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

Vous pouvez maintenant utiliser la classe comme ceci:

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

Production:

set
null
1

Cela ne peut pas être utilisé comme solution par défaut pour tous les cas, mais certainement une solution créative pour certains cas. 👍
— abhay tripathi le

et si au lieu de simplement enregistrer les valeurs, je veux les utiliser comme const calcvalue = this.options.a * 1000; cela soulèverait toujours l'alarme car il peut être potentiellement indéfini.
— Manish Rawat

Oui, vous pouvez utiliser une classe, ou l'écrire dans une autre langue, aucune des deux ne répondant à sa question.
— Rick O'Shea

22

Vous pouvez implémenter l'interface avec une classe, puis vous pouvez vous occuper de l'initialisation des membres dans le constructeur:

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

Une autre approche consiste à utiliser une fonction d'usine:

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

Ensuite, vous pouvez simplement:

var ix: IX = null;
...

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

4

Vous pouvez utiliser le Partialtype mappé comme expliqué dans la documentation: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html

Dans votre exemple, vous aurez:

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

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

si x est un objet avec des valeurs par défaut, la création d'un objet d'instance finale let a: IX = Object.assign({b:true}, x);provoquera une erreurPartial<IX> cannot be assigned to IX
— Luckylooke

Partiel change le type. xn'implémente plus IX, mais une partie de IX. Partialconvient aux endroits où chaque propriété peut être facultative, par exemple avec un ORM, où vous pouvez passer une partie d'une interface d'objet et mettre à jour uniquement les champs qui sont définis (par opposition à undefinedce que chaque champ d'un a Partialpeut être). Pour les interfaces qui ont des champs avec des valeurs par défaut, vous pouvez déclarer des littéraux d'objet implémentant ces types d'interfaces, sans avoir à déclarer les valeurs par défaut, à l'aide de la let x: Partial<IX> = { /* non-default fields */ } as IXsyntaxe.
— Paul-Sebastian Manole

1

Je suis tombé sur cela en cherchant un meilleur moyen que ce à quoi j'étais arrivé. Après avoir lu les réponses et les avoir essayées, j'ai pensé qu'il valait la peine de poster ce que je faisais car les autres réponses ne me semblaient pas aussi succinctes. Il était important pour moi de n'avoir à écrire qu'une petite quantité de code à chaque fois que je mettais en place une nouvelle interface. Je me suis installé sur ...

À l'aide d'une fonction deepCopy générique personnalisée:

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

Définissez votre interface

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

... et définissez les valeurs par défaut dans un const séparé.

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

Puis lancez comme ceci:

let x : IX = deepCopy(XDef);

C'est tout ce dont vous avez besoin.

.. cependant ..

Si vous souhaitez initialiser un élément racine de manière personnalisée, vous pouvez modifier la fonction deepCopy pour accepter les valeurs par défaut personnalisées. La fonction devient:

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

Qui peut alors être appelé comme ceci à la place:

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

Tout autre moyen préféré de copie profonde fonctionnerait. Si seulement une copie superficielle est nécessaire, Object.assign suffirait, renonçant au besoin de l'utilitaire deepCopyou de la deepCopyAssignfonction.

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

Problèmes connus

  • Il ne sera pas assigné en profondeur sous cette forme, mais il n'est pas trop difficile de modifier deepCopyAssignpour itérer et vérifier les types avant d'affecter.
  • Les fonctions et références seront perdues par le processus d'analyse / stringify. Je n'en ai pas besoin pour ma tâche et l'OP non plus.
  • Les valeurs init personnalisées ne sont pas indiquées par l'EDI ou le type vérifié lors de l'exécution.

0

Ma solution:

J'ai créé un wrapper sur Object.assign pour résoudre les problèmes de frappe.

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

Usage:

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;
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.