Standardwerte der Typoskript-Schnittstelle


137

Ich habe die folgende Oberfläche in TypeScript:

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

Ich deklariere eine Variable dieses Typs und initialisiere alle Eigenschaften

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

Dann weise ich ihnen später in einer Init-Funktion echte Werte zu

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

Aber ich mag es nicht, für jede Eigenschaft eine Reihe von Standard-Nullwerten angeben zu müssen, wenn das Objekt deklariert wird, wenn sie später nur auf echte Werte gesetzt werden sollen. Kann ich der Schnittstelle mitteilen, dass die Eigenschaften, die ich nicht angegeben habe, standardmäßig auf null gesetzt werden sollen? Was würde mich das tun lassen:

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

ohne einen Compilerfehler zu bekommen. Im Moment sagt es mir

TS2322: Typ '{}' kann nicht dem Typ 'IX' zugewiesen werden. Die Eigenschaft 'b' fehlt im Typ '{}'.


Ich habe Dokumente für Sie hinzugefügt: basarat.gitbooks.io/typescript/content/docs/tips/…
basarat

3
IMO, die Antwort stackoverflow.com/a/35074490/129196 sollte nicht der richtige Ansatz sein. Wenn Sie ein Objekt in einem Zustand haben können, in dem nicht alle Eigenschaften initialisiert wurden und dennoch gültig sind, sollten Sie diese Eigenschaften als optional deklarieren, wie in dieser Antwort angegeben: stackoverflow.com/a/43226857/129196 . Andernfalls verlieren wir den alleinigen Zweck der Verwendung von Typoskript (aus Gründen der Typensicherheit).
Charles Prakash Dasari

Antworten:


95

Kann ich der Schnittstelle mitteilen, dass die Eigenschaften, die ich nicht angegeben habe, standardmäßig auf null gesetzt werden sollen? Was würde mich das machen lassen?

Nein, aber standardmäßig sind sie undefineddas, was meistens in Ordnung ist. Sie können das folgende Muster verwenden, dh eine Typzusicherung zum Zeitpunkt der Erstellung:

let x: IX = {} as any;

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

Ich habe dieses und andere Muster hier dokumentiert: https://basarat.gitbook.io/typescript/main-1/lazyobjectliteralinitialization


Vielen Dank, ohne eine Reihe von Standardwerten zu definieren, die ich früher nicht für undefinierte Eigenschaften
festlegen konnte

42
Die Verwendung anyuntergräbt den Zweck von Type Script. Es gibt andere Antworten ohne diesen Nachteil.
Jack Miller

könnte mir jemand bei ähnlichen Fragen helfen, aber mit Generika. Hier bei dieser Frage
TAB

2
Seltsam, dass Basarat zum Beispiel 'any' passt, wenn er in dem angegebenen Link eine viel bessere Option mit 'let foo = {} as Foo;' anbietet. ('Foo' ist eine Schnittstelle)
Neurothustra

1
Das OP hat sich die Mühe gemacht, eine TypeScript-Schnittstelle zu erstellen, und fragt nach Lösungen für die Kesselbeschichtung. Ihre Lösung besteht darin, vollständig auf Schnittstellen zu verzichten? Kann auch vorschlagen, dass TypeScript auch überspringt ...
Moss Palmer

97

Sie können keine Standardwerte in einer Schnittstelle festlegen, aber Sie können das erreichen, was Sie tun möchten, indem Sie die optionalen Eigenschaften verwenden (vergleiche Absatz 3):

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

Ändern Sie einfach die Benutzeroberfläche in:

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

Sie können dann tun:

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

Verwenden Sie Ihre Init-Funktion, um Standardwerte zuzuweisen, x.bund x.cwenn diese Eigenschaften nicht festgelegt sind.


3
In der Frage wurde es gebeten, zu initialisieren x.bund x.cmit null. Wenn Sie let x = {a: 'abc'}dann schreiben x.bund x.csind undefined, erfüllt diese Antwort die Anforderungen nicht vollständig, obwohl es sich um eine intelligente schnelle Lösung handelt.
Benny Neugebauer

1
@BennyNeugebauer Die akzeptierte Antwort hat den gleichen Fehler. Dies ist die beste Antwort
Tel

1
Wenn x ein Objekt mit Standardwerten ist, führt das Erstellen eines endgültigen Instanzobjekts let a: IX = Object.assign({b:true}, x);dazu, dass b, c auch im
Instanzobjekt

Eigenschaften sollten nicht als "optional" markiert werden, um die Verwendung der Benutzeroberfläche zu vereinfachen. Die Antwort lautet "Nein". Sie können die Schnittstelle nicht "anweisen, Standardeinstellungen anzugeben", aber Sie können eine Factory-Methode zum Initialisieren einer Instanz einer Schnittstelle bereitstellen.
Rick O'Shea

34

Während die Antwort von @ Timar für nullStandardwerte (nach denen gefragt wurde) perfekt funktioniert , gibt es hier eine weitere einfache Lösung, die andere Standardwerte zulässt: Definieren Sie eine Optionsschnittstelle sowie eine entsprechende Konstante, die die Standardwerte enthält. Verwenden Sie im Konstruktor den Spread-Operator , um die optionsElementvariable festzulegen

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

Jetzt können Sie die Klasse folgendermaßen verwenden:

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

Ausgabe:

set
null
1

Dies kann nicht als Standardlösung für jeden Fall verwendet werden, aber definitiv eine kreative Lösung für einige Fälle. 👍
abhay tripathi

Was ist, wenn ich die Werte nicht nur protokollieren, sondern wie const calcvalue = this.options.a * 1000 verwenden möchte? Dies würde immer noch Alarm auslösen, da es möglicherweise undefiniert sein kann.
Manish Rawat

Ja, Sie können eine Klasse verwenden oder in einer anderen Sprache schreiben, von der keine seine Frage beantwortet.
Rick O'Shea

22

Sie können die Schnittstelle mit einer Klasse implementieren und dann die Elemente im Konstruktor initialisieren:

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

Ein anderer Ansatz ist die Verwendung einer Factory-Funktion:

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

Dann können Sie einfach:

var ix: IX = null;
...

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

4

Sie können den Partialzugeordneten Typ wie in der Dokumentation beschrieben verwenden: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html

In Ihrem Beispiel haben Sie:

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

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

Wenn x ein Objekt mit Standardwerten ist, führt das Erstellen eines endgültigen let a: IX = Object.assign({b:true}, x);Partial<IX> cannot be assigned to IX
Instanzobjekts

Teilweise ändert sich der Typ. xnicht mehr implementiert IX, sondern ein Teil von IX. PartialDies ist gut für Orte geeignet, an denen jede Eigenschaft optional sein kann, z. B. bei einem ORM, bei denen Sie einen Teil einer Objektschnittstelle übergeben und nur die definierten Felder aktualisieren können (im Gegensatz dazu kann undefinedjedes Feld eines Partialsein). Für Schnittstellen mit Feldern mit Standardwerten können Sie Objektliterale deklarieren, die diese Art von Schnittstellen implementieren, ohne die Standardwerte mithilfe der let x: Partial<IX> = { /* non-default fields */ } as IXSyntax deklarieren zu müssen .
Paul-Sebastian Manole

1

Ich stolperte darüber, als ich nach einem besseren Weg suchte als dem, zu dem ich gekommen war. Nachdem ich die Antworten gelesen und ausprobiert hatte, dachte ich, es lohnt sich zu posten, was ich tat, da sich die anderen Antworten für mich nicht so prägnant anfühlten. Es war mir wichtig, jedes Mal, wenn ich eine neue Schnittstelle einrichte, nur eine kurze Menge Code schreiben zu müssen. Ich entschied mich für ...

Verwenden einer benutzerdefinierten generischen deepCopy-Funktion:

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

Definieren Sie Ihre Schnittstelle

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

... und definieren Sie die Standardeinstellungen in einer separaten Konstante.

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

Dann Init wie folgt:

let x : IX = deepCopy(XDef);

Das ist alles was benötigt wird ..

.. jedoch ..

Wenn Sie ein Stammelement benutzerdefiniert initialisieren möchten, können Sie die deepCopy-Funktion so ändern, dass benutzerdefinierte Standardwerte akzeptiert werden. Die Funktion wird:

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

Was dann stattdessen so genannt werden kann:

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

Jede andere bevorzugte Art der Tiefenkopie würde funktionieren. Wenn nur eine flache Kopie benötigt wird, würde Object.assign ausreichen und auf die Notwendigkeit des Dienstprogramms deepCopyoder der deepCopyAssignFunktion verzichten.

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

Bekannte Probleme

  • In dieser Form wird es nicht tief zugewiesen, aber es ist nicht allzu schwierig, Änderungen deepCopyAssignvorzunehmen, um Typen vor dem Zuweisen zu iterieren und zu überprüfen.
  • Funktionen und Referenzen gehen beim Parse / Stringify-Prozess verloren. Ich brauche diese nicht für meine Aufgabe und das OP auch nicht.
  • Benutzerdefinierte Init-Werte werden von der IDE oder dem Typ, der bei der Ausführung überprüft wird, nicht angedeutet.

0

Meine Lösung:

Ich habe einen Wrapper über Object.assign erstellt, um Tippprobleme zu beheben.

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

Verwendung:

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;
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.