2017-05-12 5 views
2

Ich mag statt unveränderlicher Schnittstellen unveränderliche Klassen verwenden, aus zwei Gründen:Typoskript - Wie manipuliere ich unveränderliche Klassen?

  • so, dass ich mit ihnen einen Code bündeln kann (wie die doStuff Methode)
  • „fremde Eigenschaften“, um nicht zuzulassen (siehe Beispiel unten: egal wo es gültig ist otherThing zu setzen, mag ich, dass ein Compiler-Fehler) mit den meist tut zu kommen

Dies ist die nächste, die ich habe in der Lage gewesen, um zu bewirken, was ich will:

interface IData4 { 
    readonly thing1: string; 
    readonly thing2: string; 
} 

class Data4 implements IData4 { 
    readonly thing1: string; 
    readonly thing2: string; 

    constructor(that: IData4, props?: Partial<IData4>){ 
    Object.assign(this, that); 
    if(props){ 
     Object.assign(this, props); 
    } 
    } 

    doStuff(){ 
    return this.thing1 == this.thing2; 
    } 
} 

// more verbose than a normal ctor parameter list, but I like it better anyway 
// more readable, and it makes transposition errors less likely when all 
// the params are the same type 
// I write code to create instances rarely, reading and updating is more frequent 
let data4 = new Data4({thing1: "t1", thing2: "t2"}); 

// GOOD!: error because of "otherThing" 
// let other = new Data4({thing1: "t1", thing2: "t2", otherThing: 'blah'}); 

// GOOD!: error because missing "thing2" 
// let other = new Data4({thing1: "t1"}); 

// this is the usual update case 
let data4a = new Data4(data4, {thing2: "t2a"}); 
log.debug("data4a: " + JSON.stringify(data4a)); 

// GOOD! error because of "otherThing" 
// let other = new Data4(data4, {thing2: "t2b", otherThing: 'blah'}); 

// BAD! want "otherThing" to cause error 
// but I'm unlikely to use this construct, I wouldn't specify the spread 
// operator again because I already specified the thing to copy from 
let bad2b = new Data4(data4, {...data4, thing2: "t2b", otherThing: 'blah'}); 

// BAD! want "otherThing" to cause error 
// easy to do accidentally because I'm used to using interfaces 
let bad2c = new Data4({...data4, thing2: "t2b", otherThing: 'blah'}); 

let iData: IData4 = {thing1: 't1', thing2: 't2'}; 
// BAD! want an error about "otherThing" 
let iData2 = {...iData, thing2: 't2a', otherThing: 'blah'}; 

// GOOD! error because of "otherThing" 
// let other: IData4 = {thing1: 't1', thing2: 't2', otherThing: 'blah'}; 

// GOOD! error because lack of "thing2" 
// let other: IData4 = {thing1: 't1'}; 

Probleme mit diesem Konstrukt:

  • das gesamte Interface + Klasse Konstrukt ist unhandlich und die Eigenschaftsdefinitionen Duplizieren ist albern (plus fehleranfällig)
  • es mir noch fremden Eigenschaften angeben können, wenn ich die Verwendung "Spread" -Operator

Also, gibt es einen besseren Weg, dies zu tun?

  • Gibt es eine Möglichkeit, Funktionen mit Schnittstellen wie gewünscht zu verpacken?
  • Oder gibt es einen besseren Typ als "Partial", den ich verwenden sollte?
  • Gibt es einen Weg, ungefähr die gleiche Funktionalität wie bei Partial zu erreichen, ohne jedoch die Einstellung von Fremdeigenschaften zuzulassen?
  • Oder gibt es einen Weg in Typescript, wo ich normale Konstruktoren mit Parameterlisten verwenden kann, um ein neues Objekt mit einer geänderten Eigenschaft zu erstellen, aber nicht jede Eigenschaft angeben müssen?
    • Insbesondere, ich will nicht zu tun: let data4a = new Data4(data4.thing1, "t2a"});
+0

Die meisten funktionalen Sprachen mit einem Schwerpunkt auf Unveränderbarkeit haben ein Konstrukt, das eine neue Kopie eines Objekts erstellt, wobei einige Felder geändert werden, z. B. [F # ''' '' '' '' '' '' '' '' '.com/de-de/dotnet/articles/fsharp/sprachreferenz/copy-und-update-record-ausdrücken). JS/TS sind keine funktionalen Sprachen mit einem Schwerpunkt auf Unveränderbarkeit. Sie können es mit 'WithX (x: X)' Methoden emulieren, aber das wird mühsam, wenn Ihr Objekt viele Eigenschaften hat. Einige Projekte (z. B. der C# -Compiler) (https://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Portable/Syntax/Syntax.xml) verwenden dafür Codegeneratoren –

Antwort

0

Es ist nicht so toll, aber ich habe irgendwie eine Antwort auf die Frage „Gibt es eine Möglichkeit Funktionen zu verpacken mit Schnittstellen "?

können Sie "declaration merging" verwenden, um etwas zu machen, die eine Schnittstelle mit Funktionen annähert:

interface Data7{ 
    readonly thing1: string; 
    readonly thing2: string; 
} 

namespace Data7{ 
    export function areThingsEqualLength(value: Data7): boolean { 
    return value.thing1.length == value.thing2.length; 
    } 
} 

let data7: Data7 = {thing1: "t1", thing2: "t2"}; 
let data7a = {...data7, thing2: "t2a"}; 
log.debug("data7a: " + JSON.stringify(data7a)); 

log.debug("same length thing?: " + Data7.areThingsEqualLength(data7)); 

// GOOD! 
// error: "'otherThing' does not exist in type" 
// let other7a: Data7 = {thing1: "t1", thing2: "t2a", otherThing: 'blah'}; 

// GOOD! 
// error: "Property 'thing1' is missing in type '{ thing2: string; }'." 
// let other7b: Data7 = {thing2: 't2b'}; 

// BAD! 
// want it to fail on 'otherThing' 
let data7b = {...data7, otherThing: 'blah'}; 

// VERY BAD! 
// allows creation of an invalid object. 
// The function call will explode, even though it's written correctly 
let badData7 = {...data7, thing2: undefined}; 
// log.debug("same length thing?: " + Data7.areThingsEqualLength(badData7)); 

Sie einen Import Data7.areThingsEqualLength(data7) in areThingsEqualLength(data7) zu drehen, wenn sie bevorzugt nutzen können.

Aber ich habe auch festgestellt, dass ich die Verwendung von Schnittstellen vermeiden möchte: mit ihrem begleitenden Operator spread erlauben sie die Konstruktion von ungültigen Objekten (siehe den Kommentar "SEHR SCHLECHT!").

So bleibt die Titelfrage (vielleicht sollte das keine Antwort sein, vielleicht sollte es eine Bearbeitung der Frage sein, aber dann wäre es wirklich lang).

Verwandte Themen