Typescript: Vorschlag: Zulassen, dass get/set-Zugriffsmethoden unterschiedlichen Typs sind

Erstellt am 26. März 2015  ·  125Kommentare  ·  Quelle: microsoft/TypeScript

Es wäre großartig, wenn es eine Möglichkeit gäbe, die derzeitige Einschränkung zu lockern, dass get/set-Zugriffsmethoden denselben Typ haben müssen. das wäre hilfreich in einer situation wie dieser:

class MyClass {

    private _myDate: moment.Moment;

    get myDate(): moment.Moment {
        return this._myDate;
    }

    set myDate(value: Date | moment.Moment) {
        this._myDate = moment(value);
    }
}

Derzeit scheint dies nicht möglich zu sein, und ich muss auf Folgendes zurückgreifen:

class MyClass {

    private _myDate: moment.Moment;

    get myDate(): moment.Moment {
        return this._myDate;
    }

    set myDate(value: moment.Moment) {
        assert.fail('Setter for myDate is not available. Please use: setMyDate() instead');
    }

    setMyDate(value: Date | moment.Moment) {
        this._myDate = moment(value);
    }
}

Dies ist alles andere als ideal, und der Code wäre viel sauberer, wenn verschiedene Typen zugelassen würden.

Danke!

Design Limitation Suggestion Too Complex

Hilfreichster Kommentar

Ein JavaScript-Getter und -Setter mit verschiedenen Typen ist vollkommen gültig und funktioniert hervorragend, und ich glaube, dass dies der Hauptvorteil/Zweck dieser Funktion ist. Ein setMyDate() angeben zu müssen, nur um TypeScript zu gefallen, ruiniert es.

Denken Sie auch an reine JS-Bibliotheken, die diesem Muster folgen: Die .d.ts müssen eine Union oder any verfügbar machen.

Das Problem besteht darin, dass Accessoren in .d.ts nicht anders als normale Eigenschaften angezeigt werden

Dann sollte diese Einschränkung behoben und dieses Problem offen bleiben:

// MyClass.d.ts

// Instead of generating:
declare class MyClass {
  myDate: moment.Moment;
}

// Should generate:
declare class MyClass {
  get myDate(): moment.Moment;
  set myDate(value: Date | moment.Moment);
}

// Or shorter syntax:
declare class MyClass {
  myDate: (get: moment.Moment, set: Date | moment.Moment);
  // and 'fooBar: string' being a shorthand for 'fooBar: (get: string, set: string)'
}

Alle 125 Kommentare

Ich kann sehen, wie schön dies hier wäre (und dies wurde zuvor angefordert, obwohl ich das Problem jetzt nicht finden kann), aber es ist nicht möglich, ob das Dienstprogramm ausreicht, um dies zu rechtfertigen. Das Problem besteht darin, dass Accessoren in .d.ts nicht anders als normale Eigenschaften angezeigt werden, da sie aus dieser Perspektive gleich erscheinen. Das heißt, es gibt keine Unterscheidung zwischen Getter und Setter, sodass es keine Möglichkeit gibt, a) eine Implementierung zu fordern, die einen Accessor anstelle eines einzelnen Instanzmembers verwendet, und b) den Unterschied in den Typen zwischen Getter und Setter anzugeben.

Danke für die schnelle Antwort Dan. Ich werde den weniger eleganten Weg gehen. Danke für die tolle Arbeit!

Ein JavaScript-Getter und -Setter mit verschiedenen Typen ist vollkommen gültig und funktioniert hervorragend, und ich glaube, dass dies der Hauptvorteil/Zweck dieser Funktion ist. Ein setMyDate() angeben zu müssen, nur um TypeScript zu gefallen, ruiniert es.

Denken Sie auch an reine JS-Bibliotheken, die diesem Muster folgen: Die .d.ts müssen eine Union oder any verfügbar machen.

Das Problem besteht darin, dass Accessoren in .d.ts nicht anders als normale Eigenschaften angezeigt werden

Dann sollte diese Einschränkung behoben und dieses Problem offen bleiben:

// MyClass.d.ts

// Instead of generating:
declare class MyClass {
  myDate: moment.Moment;
}

// Should generate:
declare class MyClass {
  get myDate(): moment.Moment;
  set myDate(value: Date | moment.Moment);
}

// Or shorter syntax:
declare class MyClass {
  myDate: (get: moment.Moment, set: Date | moment.Moment);
  // and 'fooBar: string' being a shorthand for 'fooBar: (get: string, set: string)'
}

Mir ist klar, dass dies nur eine Meinung ist, aber einen Setter so zu schreiben, dass a.x === y nicht unmittelbar nach a.x = y; true gleich true ist, ist eine riesige rote Fahne. Woher wissen die Verbraucher einer solchen Bibliothek, welche Eigenschaften magische Nebenwirkungen haben und welche nicht?

Woher wissen Sie, welche Eigenschaften magische Nebenwirkungen haben und welche nicht?

In JavaScript kann es unintuitiv sein, in TypeScript beschweren sich die Tools (IDE + Compiler). Warum lieben wir TypeScript wieder? :)

Ein JavaScript-Getter und -Setter mit verschiedenen Typen ist vollkommen gültig und funktioniert hervorragend, und ich glaube, dass dies der Hauptvorteil/Zweck dieser Funktion ist.

Dies argumentiert, dass JavaScript schwach typisiert ist, also sollte TypeScript schwach typisiert sein. :-S

Dies argumentiert, dass JavaScript schwach typisiert ist, also sollte TypeScript schwach typisiert sein

C# erlaubt es und das macht diese Sprache nicht schwach typisiert . C# erlaubt kein Get/Set verschiedener Typen.

C# erlaubt es und das macht diese Sprache nicht schwach typisiert . C# erlaubt kein Get/Set verschiedener Typen.

:zwinkern:

Niemand argumentiert (soweit ich sehen kann), dass Accessoren schwach typisiert sein sollten, sie argumentieren, dass wir die Flexibilität haben sollten, den Typ (die Typen) zu definieren.

Oft ist es erforderlich, ein einfaches altes Objekt in Objektinstanzen zu kopieren.

    get fields(): Field[] {
      return this._fields;
    }

    set fields(value: any[]) {
      this._fields = value.map(Field.fromJson);
    }

Das ist viel schöner als die Alternative und ermöglicht es meinem Setter, genau die Art von Logiksetzern zu kapseln, für die sie gemacht sind.

@paulwalker das Muster, das Sie dort verwenden (ein Setter, der ein any nimmt und ein Getter, der einen spezifischeren Typ zurückgibt), ist heute gültig.

@danquirk Gut zu wissen, danke! Es sieht so aus, als müsste ich nur meinen IDE-Plugin-Compiler für ST aktualisieren.

@danquirk Das scheint laut Spielplatz (oder Version 1.6.2) nicht zu funktionieren:
http://www.typescriptlang.org/Playground#src =%0A%0Aclass%20Foo%20%7B%0A%0A%20%20get%20items()%3A%20string%5B%5D%20%7B%0A %09%20%20Rücksendung%20%5B%5D%3B%0A%20%20%7D%0A%20%20%0A%20%20Set%20Artikel(Wert%3A%20beliebig)%20%7B%0A% 09%20%20%0A%20%20%7D%0A%7D

Ich habe gerade mit typescript@next (Version 1.8.0-dev.20151102) getestet und habe auch einen Fehler.

~$ tsc --version
message TS6029: Version 1.8.0-dev.20151102
~$ cat a.ts
class A {
    get something(): number {return 5;}
    set something(x: any) {}
}

~$ tsc -t es5 a.ts
a.ts(2,2): error TS2380: 'get' and 'set' accessor must have the same type.
a.ts(3,2): error TS2380: 'get' and 'set' accessor must have the same type.

Ironischerweise hat nach der Aktualisierung meines Sublime-Linters kein Fehler mehr ausgegeben, der TypeScript 1.7.x verwendet. Ich bin davon ausgegangen, dass es sich um eine bevorstehende Verbesserung in 1.7+ handelt, also ist 1.8.0 vielleicht zurückgegangen.

Selbst mit der Version von Visual Studio Code (0.10.5 (Dezember 2015)), die Typoskript 1.7.5 unterstützt, und mit Typoskript 1.7.5, das global auf meinem Computer installiert ist, ist dies immer noch ein Problem:

image

Welche Version wird also unterstützt?
Danke

Ich glaube, Dan hat sich geirrt. Der Getter und der Setter müssen vom gleichen Typ sein.

Schande. wäre ein nettes Feature für das Schreiben von Seitenobjekten für die Verwendung in Winkelmessertests gewesen.

Ich hätte einen Winkelmessertest schreiben können:

po.email = "[email protected]";
expect(po.email).toBe("[email protected]");

... indem Sie ein Seitenobjekt erstellen:

class PageObject {
    get email(): webdriver.promise.Promise<string> {
        return element(by.model("ctrl.user.email")).getAttribute("value")
    }
    set email(value: any) {
        element(by.model("ctrl.user.email")).clear().sendKeys(value);
    }
}

Was ist mit diesem Code, der erfordert, dass der Setter vom Typ any ist?

Der Getter gibt ein webdriver.promise.Promise<string> zurück, aber der Setter, dem ich einen string -Wert zuweisen möchte.

Vielleicht macht es die folgende längere Form des Winkelmesser-Tests klarer:

po.email = "[email protected]";
var currentEmail : webdriver.promise.Promise<string> = po.email;
expect(currentEmail).toBe("[email protected]")

@RyanCavanaugh Mit der Einführung von Nullanmerkungen verhindert dies Code, der es ermöglicht, einen Setter mit null aufzurufen, um ihn auf einen Standardwert festzulegen.

class Style {
    private _width: number = 5;

    // `: number | null` encumbers callers with unnecessary `!`
    get width(): number {
        return this._width;
    }

    // `: number` prevents callers from passing in null
    set width(newWidth: number | null) {
        if (newWidth === null) {
            this._width = 5;
        }
        else {
            this._width = newWidth;
        }
    }
}

Könnten Sie zumindest in Betracht ziehen, die Typen in Gegenwart von | null und | undefined zu unterscheiden?

Das wäre wirklich ein nettes Feature.

Wird das also ein Feature sein?

@artyil es ist geschlossen und mit _By Design_ gekennzeichnet, was darauf hinweist, dass derzeit keine Pläne bestehen, es hinzuzufügen. Wenn Sie einen überzeugenden Anwendungsfall haben, der Ihrer Meinung nach die oben geäußerten Bedenken außer Kraft setzt, können Sie Ihren Fall gerne vorbringen, und zusätzliches Feedback kann das Kernteam veranlassen, seine Position zu überdenken.

@kitsonk Ich denke, in den obigen Kommentaren wurden mehr als genug überzeugende Anwendungsfälle bereitgestellt. Während das aktuelle Design mit dieser Einschränkung einem gemeinsamen Muster der meisten anderen typisierten Sprachen folgt, ist es im Zusammenhang mit Javascript unnötig und übermäßig restriktiv. Während es beabsichtigt ist, ist das Design falsch.

Ich stimme zu.

Nachdem ich noch etwas darüber nachgedacht habe. Ich denke, das Problem hier ist wirklich die Komplexität der Implementierung. Ich persönlich finde das Beispiel von @Arnavion überzeugend, aber das heutige Typsystem behandelt Getter/Setter als reguläre Eigenschaften. Damit dies funktioniert, sollten beide den gleichen Wert haben. Einen Lese-/Schreibtyp zu unterstützen, wäre eine große Änderung, ich bin mir nicht sicher, ob das Dienstprogramm hier die Implementierungskosten wert wäre.

Obwohl ich TypeScript liebe und all die Mühe schätze, die das Team hineinsteckt (wirklich, ihr rockt!), muss ich zugeben, dass ich von dieser Entscheidung enttäuscht bin. Es wäre eine viel bessere Lösung als die Alternativen von getFoo()/setFoo() oder get foo()/set foo()/setFooEx() .

Nur eine kurze Liste von Problemen:

  • Wir gehen derzeit davon aus, dass Eigenschaften genau einen Typ haben. Wir müssten jetzt zwischen dem „Lesen“-Typ und dem „Schreiben“-Typ jeder Eigenschaft an jedem Standort unterscheiden
  • Alle Typbeziehungen werden wesentlich komplexer, da wir über zwei Typen pro Eigenschaft statt über einen nachdenken müssen (ist { get foo(): string | number; set foo(): boolean } { foo: boolean | string | number } #$ zuweisbar oder umgekehrt?)
  • Wir gehen derzeit davon aus, dass eine Eigenschaft, nachdem sie gesetzt wurde, immer noch den Typ des gesetzten Werts in der folgenden Zeile hat (was anscheinend eine falsche Annahme in den Codebasen einiger Leute ist, was?). Wir müssten wahrscheinlich einfach jede Flusssteuerungsanalyse für Eigenschaften wie diese "ausschalten".

Ehrlich gesagt versuche ich wirklich, hier keine Vorschriften zum Schreiben von Code zu machen, aber ich lehne die Idee wirklich ab, dass dieser Code

foo.bar = "hello";
console.log(foo.bar);

sollte jemals etwas anderes als "hello" in einer Sprache drucken, die versucht, eine vernünftige Semantik zu haben. Eigenschaften sollten ein Verhalten aufweisen, das für externe Beobachter nicht von Feldern zu unterscheiden ist.

@RyanCavanaugh , obwohl ich Ihnen bei der eingespritzten Meinung zustimme, sehe ich ein Gegenargument, das _möglicherweise_ nur sehr TypeScripty ist ... Etwas schwach getipptes auf einen Setter zu werfen, aber immer etwas stark getipptes zurückzugeben, z.

foo.bar = [ '1', 2 ];  // any[]
console.log(foo.bar);  // number[]: [ 1, 2 ]

Obwohl ich persönlich dazu neige zu denken, wenn Sie so magisch sein werden, ist es am besten, eine Methode zu entwickeln, damit der Endentwickler klar verstehen kann, was gebogen, gefaltet und verstümmelt wird.

Hier ist unser Anwendungsfall für diese Funktion. Unsere API hat eine Funktion eingeführt, die wir _Autocasting_ genannt haben. Der Hauptvorteil ist eine optimierte Entwicklererfahrung, die die Anzahl der zu importierenden Klassen eliminieren kann, um Eigenschaften zuzuweisen, für die der Typ gut definiert ist.

Beispielsweise kann eine Farbeigenschaft als Color -Instanz oder als CSS-String wie rgba(r, g, b, a) oder als Array aus 3 oder 4 Zahlen ausgedrückt werden. Die Eigenschaft wird weiterhin als Instanz von Color typisiert, da dies der Typ dessen ist, was Sie beim Lesen des Werts erhalten.

Einige Informationen dazu: https://developers.arcgis.com/javascript/latest/guide/autocasting/index.html

Unsere Benutzer waren sehr glücklich über diese Funktion, die die Anzahl der erforderlichen Importe reduziert, und verstehen vollkommen, dass der Typ die Zeile nach der Zuweisung ändert.

Ein weiteres Beispiel für dieses Problem: https://github.com/gulpjs/vinyl#filebase

file.base = 'd:\\dev';
console.log(file.base); //  'd:\\dev'
file.base = null;
console.log(file.base); //  'd:\\dev\\vinyl' (returns file.cwd)

Der Setter ist also string | null | undefined und der Getter ist nur string . Welchen Typ sollten wir in den Typdefinitionen für diese Bibliothek verwenden? Wenn wir Ersteres verwenden, würde der Compiler überall nutzlose Nullprüfungen erfordern. Wenn wir Letzteres verwenden, können wir dieser Eigenschaft null nicht zuweisen.

Ich habe ein weiteres Beispiel, bei dem ich möchte, dass der Getter nullable zurückgibt, der Setter jedoch niemals null als Eingabe zulassen sollte, stilisiert als:

class Memory {
    public location: string;
    public time: Date;
    public company: Person[];
}

class Person
{
    private _bestMemoryEver: Memory | null;

    public get bestMemoryEver(): Memory | null { // Might not have one yet
        return this._bestMemoryEver;
    }

    public set bestMemoryEver(memory: Memory) { // But when he/she gets one, it can only be replaced, not removed
        this._bestMemoryEver = memory;
    }
}

var someDude = new Person();
// ...
var bestMemory: Memory | null = someDude.bestMemoryEver;
//...
someDude.bestMemoryEver = null; // Oh no you don't!

Ich verstehe, dass es zu viel Arbeit sein könnte, eine spezielle Logik zu erstellen, um zuzulassen, dass sich Getter/Setter auf null unterscheiden, und es ist keine so große Sache für mich, aber es wäre schön, es zu haben.

@Elephant-Vessel philosophisch mag ich das Beispiel absolut, es repräsentiert die unbeständige Natur des Menschen gut, aber ich bin nicht überzeugt, dass es das nicht noch besser darstellen würde, wenn es null (oder undefined ) einzustellen. Wie kann ich einen Synapsenausfall im System modellieren?

@aluanhaddad Warum willst du einen Synapsenausfall? Ich will solche schlechten Sachen nicht in meinem Universum ;)

Irgendwelche Updates dazu? Was ist mit einem Standardwert, wenn er auf null oder undefiniert gesetzt ist?

Ich möchte nicht, dass der Verbraucher vorher eine Nullprüfung durchführen muss. Derzeit muss ich den Setter stattdessen zu einer separaten Methode machen, aber ich möchte, dass sie gleich sind.

Unten ist, was ich haben möchte:

export class TestClass {
  private _prop?: number;

  get prop(): number {
    // return default value if not defined
    this._prop === undefined ? 0 : this._prop;
  }
  set prop(val: number | undefined) {
    this._prop = val;
  }
}

Es scheint mir, dass der Vorteil der Nicht-Null-Überprüfung mit Fallstricken einhergeht, und dies ist einer davon. Wenn die strenge Nullüberprüfung deaktiviert ist, ist dies möglich, aber Sie erhalten keine Compiler-Hilfe, um Ausnahmen von Nullverweisen zu verhindern. Wenn Sie jedoch Compilerunterstützung wünschen, sollte dies meiner Meinung nach mit mehr Unterstützung einhergehen, z. B. mit separaten Definitionen für Getter und Setter in Bezug auf zumindest die Nullfähigkeit, wenn nichts anderes.

Die Etiketten auf dem Problem weisen darauf hin, dass es sich um eine Designbeschränkung handelt und die Implementierung als zu komplex angesehen wird, was im Wesentlichen bedeutet, dass es nirgendwohin führt, wenn jemand einen super zwingenden Grund hat, warum dies der Fall sein sollte.

@mhegazy @kitsonk Ich bin vielleicht voreingenommen, aber ich denke, dies ist ein Fehler , der für die strenge Nullprüfung nach einem gemeinsamen Muster aufgetaucht ist, insbesondere in anderen geschweiften Klammern wie Sprachen, in denen sie noch keine Nullprüfung haben. Eine Problemumgehung würde erfordern, dass der Verbraucher den Bang-Operator verwendet oder überprüft, ob er tatsächlich niemals null ist (was der Punkt ist, der bei der Verwendung von Standardwerten niemals null ist).

Dies bricht zusammen, sobald Sie eine strenge Nullprüfung hinzufügen, da es sich jetzt um technisch unterschiedliche Typen handelt. Ich verlange nicht, dass starke unterschiedliche Typen festgelegt werden, aber es scheint, als würden die Designanforderungen, um dies zu ermöglichen, auch starke unterschiedliche Typen ermöglichen.

Für schwache Typen könnte ein alternatives Design verwendet werden, so dass Typen wie null und undefined für Schnittstellendefinitionen und d.ts-Dateien einen Sonderfall erhalten würden, wenn sie nicht vollständig unterschiedliche Typen aktivieren möchten.

Als Antwort auf https://github.com/Microsoft/TypeScript/issues/2521#issuecomment -199650959
Hier ist ein Vorschlagsdesign, das weniger komplex zu implementieren sein sollte:

export interface Test {
  undefset prop1: number; // property [get] type number and [set] type number | undefined
  nullset prop2: number; // property [get] type number and [set] type number | null
  nilset prop3: number; // property [get] type number and [set] type number | null | undefined
  undefget prop4: number; // property [get] type number | undefined and [set] type number
  nullget prop5: number; // property [get] type number | null and [set] type number
  nilget prop6: number; // property [get] type number | null | undefined and [set] type number
}

Es sieht so aus, als ob einige Leute diesen Thread beobachten, die mit TypeScript viel vertrauter sind, also kann vielleicht jemand, der immer noch aufpasst, eine verwandte Frage beantworten. In diesem Cäsium-Problem habe ich die Einschränkung des get/set-Typs erwähnt, die wir hier diskutieren, und die Cäsium-Leute sagten, dass das Muster, dem sie folgen, von C# stammt – es ist der implizite Konstruktor .

Kann TypeScript implizite Konstruktoren unterstützen? Das heißt, kann ich sagen, dass myThing.foo immer Bar zurückgibt und Bar direkt zugewiesen werden kann, aber auch number zugewiesen werden kann, was dies tut leise eingewickelt / verwendet werden, um ein Bar zu initialisieren, als Annehmlichkeit für den Entwickler? Wenn es möglich ist, dies zu tun, indem Bar kommentiert wird oder vielleicht ausdrücklich gesagt wird, dass " number Bar<number> zuweisbar ist ", würde dies den in der Caesium-Ausgabe diskutierten Anwendungsfall ansprechen. und auch viele der in diesem Thread angesprochenen Probleme.

Wenn nicht, muss ich die implizite Konstruktorunterstützung in einer separaten Ausgabe vorschlagen?

Je mehr ich darüber nachdenke / lese, desto mehr bin ich mir sicher, dass das "implizite Konstruktormuster" das in dieser Ausgabe beschriebene Feature benötigen wird. Die einzige Möglichkeit in Vanilla JS ist die Verwendung von Get/Set-Accessoren für Objekte, da dies das einzige Mal ist, dass die nominelle Zuweisung (Operator = ) tatsächlich eine benutzerdefinierte Funktion aufruft. (Richtig?) Also, ich denke, wir werden wirklich brauchen

class MyThing{
  set foo(b: Bar<boolean> | boolean);
  get foo(): Bar<boolean>;
}

was sich so anhört, als ob @RyanCavanaugh denkt, dass es keine "gesunde Semantik" ist.

Die einfache Tatsache ist, dass es eine ziemlich beliebte JS-Bibliothek gibt, die dieses Muster verwendet, und es sieht so aus, als wäre es schwierig, wenn nicht unmöglich, die bestehenden TS-Einschränkungen zu beschreiben. Ich hoffe, ich liege falsch.

JavaScript erlaubt bereits das von Ihnen beschriebene Muster. Die _Herausforderung_ besteht darin, dass die Lese- und Schreibseite von Typen in TypeScript per Design als gleich angenommen werden. Es gab eine Änderung an der Sprache, um Zuweisungen zu verbieten ( readonly ), aber es gibt ein paar Probleme, die das _nur-Schreiben_-Konzept erfordert haben, die als zu komplex für wenig realen Wert diskutiert wurden.

IMO, seit JavaScript Accessoren erlaubt, haben Leute potenziell verwirrende APIs mit ihnen erstellt. Ich persönlich finde es verwirrend, dass sich etwas bei einem Auftrag _magisch_ in etwas anderes ändert. Alles implizite, insbesondere die Typkonvertierung, ist meiner Meinung nach der Fluch von JavaScript. Gerade die Flexibilität macht Probleme. Bei dieser Art von Konvertierungen, bei denen es _Magie_ gibt, sehe ich persönlich gerne aufgerufene Methoden, bei denen es für den Verbraucher etwas expliziter ist, dass eine Art von ✨ auftritt und einen Nur-Lese-Getter zum Abrufen von Werten erstellt.

Das bedeutet nicht, dass es keine reale Nutzung gibt, das ist potenziell vernünftig und vernünftig. Ich denke, es kommt auf die Komplexität an, das gesamte Typsystem in zwei Teile aufzuteilen, wo Typen auf ihre Lese- und Schreibbedingungen hin verfolgt werden müssen. Das scheint ein sehr nicht triviales Szenario zu sein.

Ich stimme den :sparkles: hier zu, aber ich argumentiere nicht für oder gegen die Verwendung des Musters, ich versuche nur, hinter Code zu kommen, der es bereits verwendet, und seine Form zu beschreiben. Es funktioniert bereits so, wie es funktioniert, und TS gibt mir nicht die Werkzeuge, um es zu beschreiben.

Auf Gedeih und Verderb hat uns JS die Möglichkeit gegeben, Zuweisungen in Funktionsaufrufe umzuwandeln, und die Leute nutzen es. Ohne die Möglichkeit, set/get -Paaren unterschiedliche Typen zuzuweisen, warum sollte man überhaupt set/get in Umgebungstypisierungen haben? Salsa muss nicht wissen, dass eine Eigenschaft mit Getter und Setter implementiert ist, wenn es myThing.foo immer als Mitgliedsvariable eines einzigen Typs behandelt, unabhängig davon, auf welcher Seite der Zuweisung es sich befindet. (Offensichtlich ist die eigentliche TypeScript-Kompilierung eine ganz andere Sache.)

@thw0rted

Es sieht so aus, als ob einige Leute diesen Thread beobachten, die mit TypeScript viel vertrauter sind, also kann vielleicht jemand, der immer noch aufpasst, eine verwandte Frage beantworten. In diesem Cäsium-Problem habe ich die Einschränkung des get/set-Typs erwähnt, die wir hier diskutieren, und die Cäsium-Leute sagten, dass das Muster, dem sie folgen, von C# stammt – es ist der implizite Konstruktor.

Die impliziten benutzerdefinierten Konvertierungsoperatoren von C# führen eine statische Codegenerierung basierend auf den Typen von Werten durch. TypeScript-Typen werden gelöscht und beeinflussen das Laufzeitverhalten nicht ( async / await Grenzfälle für Promise Polyfiller nicht beständig).

@kitsonk Ich muss in Bezug auf Eigenschaften im Allgemeinen nicht zustimmen. Nachdem ich ziemlich viel Zeit mit Java, C++ und C# verbracht habe, liebe ich Eigenschaften (wie in C# zu sehen) absolut, weil sie eine kritische syntaktische Abstraktion bieten (dies trifft in gewisser Weise auf JavaScript zu). Sie ermöglichen es Schnittstellen, Lese-/Schreibfunktionen auf sinnvolle Weise zu trennen, ohne die Syntax zu ändern. Ich hasse es, Verben zu sehen, die für triviale Operationen wie getX() verschwendet werden, wenn das Erhalten X implizit sein kann.
IMO verwirrende APIs, von denen viele, wie Sie sagen, Missbrauch von Accessoren tun, stammen eher von zu vielen _Settern_, die magische Dinge tun.

Wenn ich eine schreibgeschützte, aber Live-Ansicht über einige Daten habe, sagen wir registry , denke ich, dass schreibgeschützte Eigenschaften sehr einfach zu verstehen sind.

interface Entry {key: string; value: any;}

export function createRegistry() {
  let entries: Entry[] = [];
  return {
    register(key: string, value: any) {
      entries = [...entries, {key, value}];
    },
    get entries() {
      return [...entries];
    }
  }
}

const registry = createRegistry();

registry.register('hello', '您好');
console.log(registry.entries); //[{key: 'hello', value: '您好'}]
registry.register('goodbye', '再见');
console.log(registry.entries); //[{key: 'hello', value: '您好'}, {key: 'goodbye', value: '再见'}]

Entschuldigung für die Tangente, aber ich liebe Accessors dafür und denke, dass sie leicht zu verstehen sind, aber ich bin bereit, mich vom Gegenteil überzeugen zu lassen, und Lesbarkeit ist mein erstes Anliegen.

Wenn TypeScript JavaScript einschränkt, wird es eher zu einem Ärgernis als zu einem Vorteil. Ist TypeScript nicht dazu gedacht, Entwicklern zu helfen, miteinander zu kommunizieren?

Außerdem werden Setter aus einem bestimmten Grund Mutatoren genannt. Wenn ich keine Konvertierung bräuchte, würde ich keinen Setter verwenden, ich würde die Variable selbst setzen.

Javascript-Projekt auf Typoskript portieren. Ich bin auf dieses Problem gestoßen..

Dies wäre schön für eckige @Input- Dekoratoren. Da der Wert von einer Vorlage übergeben wird, wäre es meiner Meinung nach viel sauberer, mit verschiedenen eingehenden Objekttypen umzugehen.

Update: Das scheint bei mir zu funktionieren

import { Component, Input } from '@angular/core';
import { flatMap, isString, isArray, isFalsy } from 'lodash';

@Component({
  selector: 'app-error-notification',
  templateUrl: './error-notification.component.html',
})

export class ErrorNotificationComponent {
  private _errors: Array<string> = [];
  constructor() { }
  /**
   * 'errors' is expected to be an input of either a string or an array of strings
   */
  @Input() set errors(errors: Array<string> | any){
      // Caller just passed in a string instead of an array of strings
      if (isString(errors)) {
        this._errors = [errors];
      }
      // Caller passed in array, assuming it is a string array
      if (isArray(errors)) {
        this._errors = errors;
      }
      // Caller passed in something falsy, which means we should clear error list
      if (isFalsy(errors)) {
        this._errors = [];
      }
      // At this point just set it to whatever might have been passed in and let
      // the user debug when it is broken.
      this._errors = errors;
  }

  get errors() {
    return this._errors;
  }
}

Also waren wir? Ich würde wirklich gerne die Möglichkeit haben, mit dem Getter einen anderen Typ zurückzugeben als vom Setter. Zum Beispiel:

class Field {
  private _value: string;

  get value(): string {
    return this._value;
  }

  set value(value: any) {
    this._value = String(value);
  }
}

Dies ist, was 99% der nativen Implementierungen tun (wenn Sie eine Zahl an (input as HTMLInputElement).value übergeben, wird immer eine Zeichenfolge zurückgegeben. Tatsächlich sollten get und set als Methoden betrachtet werden und einige zulassen:

set value(value: string);
set value(value: number);
set value(value: any) {
  this._value = String(value);
}
  // AND/OR
set value(value: string | number) {
  this._value = String(value);
}

@raysuelzer , wenn Sie sagen, dass Ihr Code "funktioniert", gibt ErrorNotificationComponent.errors nicht einen Typ von Array<string> | any zurück? Das bedeutet, dass Sie jedes Mal, wenn Sie es verwenden, Typwächter benötigen, wenn Sie wissen, dass es immer nur Array<string> zurückgeben kann.

@lifaon74 , soweit ich weiß, gibt es keine Bewegung zu diesem Thema. Ich denke, es wurde ein überzeugender Fall vorgebracht – mehrere Szenarien wurden vorgestellt, Tonnen von veraltetem JS-Code, der aus diesem Grund nicht richtig in Typescript beschrieben werden kann – aber das Problem ist geschlossen. Wenn Sie glauben, dass sich die Fakten vor Ort geändert haben, eröffnen Sie vielleicht eine neue? Ich kenne die Strategie des Teams nicht, alte Argumente neu zu erfinden, aber ich würde hinter Ihnen stehen.

Ich kenne die Strategie des Teams nicht, alte Argumente neu zu erfinden, aber ich würde hinter Ihnen stehen.

Sie werden zuvor geschlossene Themen überdenken. Das Anbringen eines 👍 am Anfang der Ausgabe zeugt davon, dass es sinnvoll ist. Ich glaube, dass es im Allgemeinen in die Kategorie "zu komplex" gefallen ist, weil dies bedeuten würde, dass das Typsystem bei jedem Lesen und Schreiben gegabelt werden müsste, und ich vermute, das Gefühl ist der Aufwand und die Kosten, die erforderlich sind, um das zu sättigen, was ist ein gültiger, aber etwas ungewöhnlicher Anwendungsfall ist es nicht wert.

Mein persönliches Gefühl ist, dass es ein _nice to have_ wäre, insbesondere für die Möglichkeit, bestehenden JavaScript-Code zu modellieren, der dieses Muster effektiv verwendet.

Ich persönlich würde die Angelegenheit als gelöst betrachten, wenn wir eine nicht ganz umständliche Problemumgehung für das alte JS-Problem finden würden. Ich bin immer noch ein TS-Greenhorn, also ist meine aktuelle Lösung (erzwungenes Casting oder unnötige Type Guards) vielleicht keine optimale Nutzung vorhandener Fähigkeiten?

Zustimmen. Die setter werden so stark eingeschränkt, wenn der Typ derselbe sein muss ... Bitte verbessern.

Suchen Sie nach Empfehlungen, um dies zu erreichen:

get price() {
    return (this._price as number);
  }

  set price(price) {
    this._price = typeof price === 'string' ? parseFloat(parseFloat(price).toFixed(8)) : parseFloat(price.toFixed(8));
  }

Ich möchte, dass der gespeicherte Wert eine Zahl ist, aber ich möchte im Setter von String in Float konvertieren, damit ich Float nicht jedes Mal analysieren muss, wenn ich eine Eigenschaft setze.

Ich denke, das Urteil lautet, dass dies ein gültiges (oder zumindest einigermaßen akzeptiertes) Javascript-Muster ist, das nicht gut zu Typescript-Interna passt. Wenn das Ändern der Interna zu groß ist, könnten wir vielleicht eine Art Anmerkung erhalten, die die interne Kompilierung des TS ändert? Etwas wie

class Widget {
  get price(): number | /** <strong i="7">@impossible</strong> */ string | undefined { return this._price; }
  set price(val: number|string|undefined){ ... }
}

let w = new Widget();
w.price = 10;
// Annotation processes as "let p:number|undefined = (w.price as number|undefined)"
let p: number|undefined = w.price;

Mit anderen Worten, vielleicht könnten wir den TS so auszeichnen, dass der Präprozessor (?) alle Lesevorgänge von w.price in eine explizite Umwandlung umwandelt, bevor der TS in JS transpiliert wird. Natürlich kenne ich die Interna, wie TS Anmerkungen verwaltet, nicht, also könnte dies totaler Müll sein, aber die allgemeine Idee ist, dass es vielleicht einfacher wäre, den TS irgendwie in andere TS zu zaubern, als zu ändern, wie der TS-Transpiler generiert JS.

Ich sage nicht, dass diese Funktion nicht notwendig ist, aber hier ist eine Möglichkeit, sie für set zu umgehen. IMO get sollte immer denselben Variablentyp zurückgeben, also war das gut genug für mich.

class Widget {
    get price(): number { return this._price; }
    set price(val){ return this.setPrice(val); } // call another function

    // do processing here
    private setPrice(price: number | string): number {
        let num = Number(price);
        return isNaN(num) ? 0 : num;
    }
}

@iamjoyce widget.price = '123' gibt den Kompilierungsfehler in Ihrem Code aus

@iamjoyce => falsch, weil der Compiler val: number in set price(val) annimmt

Ja, ich bin auch hier, weil Angulars Verwendung von Komponenten anscheinend will, dass wir unterschiedliche Getter/Setter-Typen haben.

Angular fügt immer eine Zeichenfolge ein, wenn Sie eine Komponenteneigenschaft (über ein Attribut) aus dem Markup festlegen (es sei denn, Sie verwenden einen Bindungsausdruck), unabhängig vom Typ der Eingabeeigenschaft. Es wäre also auf jeden Fall schön, dies so modellieren zu können:

private _someProperty: SomeEnum;
set someProperty(value: SomeEnum | string) {
   this._someProperty = this.coerceSomeEnum(value);
} 
get someProperty(): SomeEnum {
  return this._someProperty;
}

Heute funktioniert dies, wenn wir das | string weglassen, aber das dort zu haben, würde genauer beschreiben, wie Angular die Komponente letztendlich verwendet. Wenn Sie über Code darauf zugreifen, spielt der Typ der Eigenschaft eine Rolle, aber wenn Sie sie wie ein Attribut festlegen, wird eine Zeichenfolge brutal erzwungen.

Ich glaube nicht, dass wir diese Funktionalität im Allgemeinen wollen, weil wir APIs auf diese Weise entwerfen möchten. Ich stimme zu, dass Eigenschaften besser sind, wenn sie keine Zwangsnebenwirkungen unter der Decke haben. Um dies zu umgehen, wäre es großartig, wenn Angular so konzipiert worden wäre, dass Attributsätze im Vergleich zu verbindlichen Eigenschaftssätzen an verschiedenen Einstiegspunkten eingehen würden.

Aber wenn wir das pragmatisch betrachten, ist es hilfreich, wenn TypeScript es uns ermöglicht, die Interaktionen mit externen Bibliotheken zu modellieren, die zufällig unsere Typen auf eine Weise verwenden, die dem Axiom der Gleichheit von Lese-/Schreibtypen widerspricht.

In diesem Fall wäre es sehr problematisch, den Union-Typ auf dem Getter zu verwenden, da dies alle möglichen lästigen Typwächter/Assertionen erfordern würde. Aber den Union-Typ wegzulassen, fühlt sich für den Setter falsch an, da jedes Werkzeug später entscheiden könnte, zu versuchen, zu überprüfen, ob Eigenschaften, die von Attributen gesetzt werden, von Strings zuweisbar sein sollten.

In diesem Fall ist das Typsystem also nicht ausdrucksstark genug, um zu erfassen, wie eine externe Bibliothek den Typ verwenden darf. Dies spielt _unmittelbar_ keine Rolle, da die externe Bibliothek diese Typen nicht wirklich auf Typoskriptebene mit Typinformationen verarbeitet. Aber kann letztendlich eine Rolle spielen, da die Werkzeuge sehr wohl die Typinformationen verbrauchen können.

Eine Person oben erwähnte eine alternative Lösung, die ein wenig unausstehlich erscheint, aber wahrscheinlich funktionieren könnte, wobei wir den Vereinigungstyp sowohl für den Setter als auch für den Getter verwenden könnten, aber eine Möglichkeit haben, anzuzeigen, dass bestimmte Typen aus der Vereinigung für den Getter unmöglich sind. Sie werden also im Getter-Call-Site-Site-Control-Flow vorab von der Berücksichtigung ausgeschlossen, als ob jemand einen Typwächter verwendet hätte, um zu überprüfen, ob sie nicht anwesend sind. Das scheint jedoch ärgerlich zu sein, wenn man eine Superset-Vereinigung auf dem Setter im Vergleich zu einer Subset-Vereinigung auf dem Getter zulässt.

Aber vielleicht ist das ein Weg, die Dinge intern zu lösen. Solange der Setter nur eine Obermengenvereinigung des Getter-Typs ist. Behandeln Sie die Getter- und Setter-Typen intern als gleichwertig, markieren Sie jedoch Teile des Union-Typs des Getters als unmöglich. Damit die Kontrollflussanalyse sie aus der Betrachtung herausnimmt. Würde das die Designbeschränkungen umgehen?

Um das oben zu erläutern. Vielleicht wäre es vom Standpunkt der Typaussage aus nützlich, Teile eines zusammengesetzten Typs als unmöglich kennzeichnen zu können. Dies würde die Gleichheit mit einem anderen Typ mit derselben Struktur, aber ohne die unmöglichen Modifikatoren, nicht beeinträchtigen. Ein unmöglicher Modifikator würde sich nur auf die Kontrollflussanalyse auswirken.

Als alternativer Vorschlag würde dies auch ausreichen, wenn es eine Syntax zum Anwenden eines benutzerdefinierten Typwächters auf einen Rückgabewert gäbe. Mir ist klar, dass dies unter normalen Umständen etwas lächerlich ist, aber diese Art von Ausdruckskraft sowohl beim Rückgabewert als auch bei den Argumenten zu haben, würde helfen, diese Grenzfälle zu lösen.

Der benutzerdefinierte Typwächter für den Rückgabewert hat auch den Vorteil, etwas zu sein, das in Typdeklarationen/Schnittstellen ausgedrückt werden kann, wenn es in eine Eigenschaft gequetscht wird?

Fügen Sie hier einfach einen weiteren Anwendungsfall hinzu, mobx-state-tree ermöglicht das Festlegen einer Eigenschaft auf verschiedene Arten (von Instanzen und Snapshot-Typen), gibt sie jedoch immer nur auf eine einzige Standardmethode (Instanzen) vom Get-Accessor zurück. Dies wäre also äußerst nützlich, wenn es unterstützt würde.

Ich komme so herum:

interface SomeNestedString {
  foo: string;
}

...

private _foo: SomeNestedString | string;

get foo(): SomeNestedString | string {
  return this._foo;
}

set foo(value: SomeNestedString | string) {
  this._foo = (value as SomeNestedString).foo;
}

Ich glaube nicht, dass das das vorliegende Problem umgeht. Sie benötigen immer noch Typwächter, wenn Sie den Getter verwenden, obwohl der Getter in Wirklichkeit immer nur eine Teilmenge des Union-Typs zurückgibt.

Ich verwende any , um TSLint zu umgehen. Und der Compiler meckert auch nicht.

export class FooBar {
  private bar: string;

  get foo (): string | any {
    return this.bar;
  }

  set foo (value: Date | string | any) {
    // Type guarding enables IntelliSense in VS Code
    if (value instanceof Date) {
      this.bar = value.toISOString();
    } else if (typeof value === 'string') {
      this.bar = value;
    } else {
      this.bar = String(value); // Or throw an error
    }
  }
}

@jpidelatorre Auf diese Weise verlieren Sie die Typsicherheit.

const fooBar = new FooBar()
const a: number = fooBar.foo // works while it should fail
fooBar.foo = 123 // fails only at runtime, not compile time. It doesnt fail in this particular case with strings and numbers, but it will with something more complex

@keenondrums Das ist genau der Grund, warum wir möchten, dass Accessoren unterschiedliche Typen haben. Was ich gefunden habe, ist ein Workaround, keine Lösung.

@jpidelatorre meine aktuelle Problemumgehung besteht darin, eine andere Funktion als Setter zu verwenden

export class FooBar {
  private bar: string;

  get foo (): string {
    return this.bar;
  }

  setFoo (value: Date | string ) {}
}

@keenondrums Nicht so gut aussehend wie Accessors, aber die bisher beste Option.

Nicht wirklich ein Modell dafür, was mit @input -Eigenschaften auf einer Angular-Komponente passiert, es sei denn, Sie verwenden eine separate Funktion für einen Getter.

Was einfach zu hässlich ist.

Ich würde diese Funktion auch mögen, brauche sie aber, um in .d.ts-Dateien gut zu funktionieren, wo es keine Problemumgehungen gibt. Ich versuche, einige Klassen zu dokumentieren, die über Mocha (die Objective-C/Javascript-Brücke) bereitgestellt werden, und Instanzeigenschaften von umschlossenen Elementen werden wie folgt eingerichtet:

class Foo {
    get bar:()=>number;
    set bar:number;
}

const foo = new Foo();
foo.bar = 3;
foo.bar(); // 3

Ich habe auch viele Fälle gesehen, in denen eine API es Ihnen ermöglicht, eine Eigenschaft mit einem Objekt festzulegen, das einer Schnittstelle entspricht, aber der Getter gibt immer eine echte Klasseninstanz zurück:

interface IFoo {
    bar: string;
}

class Foo implements IFoo {
    bar: string;
    toString():string;
}

class Example {
    get foo:Foo;
    set foo:Foo|IFoo;
}

Ich hatte dieses Thema weitgehend vergessen, aber beim Durchlesen kam mir ein Gedanke. Ich denke, wir haben uns auf die Idee geeinigt, dass dies abstrakt sinnvoll, aber technisch zu kompliziert ist, um machbar zu sein. Das ist eine Kompromisskalkulation – es ist technisch nicht unmöglich , es lohnt sich einfach nicht, Zeit und Mühe (und zusätzliche Codekomplexität) zu implementieren. Rechts?

Ändert die Tatsache, dass es dadurch unmöglich wird, die Kern-DOM-API genau zu beschreiben, die Mathematik überhaupt? @RyanCavanaugh sagt

Ich lehne wirklich die Idee ab, dass dieser Code foo.bar = "hello"; console.log(foo.bar); jemals etwas anderes als „Hallo“ in einer Sprache ausgeben sollte, die versucht, eine vernünftige Semantik zu haben.

Wir könnten darüber streiten, ob es auf diese Weise verwendet werden sollte , aber das DOM hat immer Konstrukte wie el.hidden=1; console.log(el.hidden) // <-- true, not 1 unterstützt. Dies ist nicht nur ein Muster, das ein paar Leute verwenden, es ist nicht nur, dass es in einer beliebten Bibliothek ist, also könnte es eine gute Idee sein, es zu unterstützen. Es ist ein Grundprinzip, wie JS schon immer funktioniert hat – ein bisschen DWIM, das in die Seele der Sprache eingebrannt ist – und es auf Sprachebene unmöglich zu machen, bricht das Grundprinzip von TS, dass es eine „Obermenge“ von JS sein muss . Es ist eine hässliche Blase, die aus dem Venn-Diagramm herausragt, und wir sollten sie nicht vergessen.

Aus diesem Grund gefällt mir immer noch die Idee, den Setter / Getter vom gleichen Typ zu halten:

number | boolean

Aber die Einführung einer Art Typeguard, wo Sie angeben können, dass der Getter zwar technisch denselben Union-Typ hat, aber in Wirklichkeit immer nur eine Teilmenge der Typen in der Union zurückgibt. Macht es die Behandlung als eine Art schmaler Schutz auf dem Getter (ein Inferenzfluss-Ding?) nicht einfacher als eine Modifikation des Typenmodells? (Er sagt, nichts über die Interna zu wissen ...)

Dies könnte alternativ nur impliziert werden, wenn der auf dem Getter verwendete Typ eine strikte Teilmenge des Setter-Typs wäre?

Dies könnte alternativ nur impliziert werden, wenn der auf dem Getter verwendete Typ eine strikte Teilmenge des Setter-Typs wäre?

👍 !

Ich denke, wir sind uns alle einig, dass Sie nicht in der Lage sein sollten, einen Typ zu erhalten, der nicht einstellbar war. Ich hätte nur gerne eine Möglichkeit, den Typ auf get automatisch so einzuschränken, dass er der Typ ist, von dem ich weiß, dass er immer sein wird.

Ich verstehe nicht, warum einige Einwände erheben, dass dies gegen die Typensicherheit verstoßen würde. Ich sehe nicht, dass es gegen die Typsicherheit verstößt, wenn wir zumindest dem Setter erlauben, einen anderen Typ zu haben, denn wie auch immer, wir haben eine Überprüfung vorgenommen, die Sie nicht zulassen, dass andere Typen auf eine Eigenschaft gesetzt werden.
Ex:
Angenommen, ich habe eine Eigenschaft als Arrayaber von DB wird dies als Zeichenfolge mit Kommatrennung zurückgegeben, z. B. '10,20,40'. Aber ich kann das jetzt nicht der Mode-Eigenschaft zuordnen, daher wäre es sehr hilfreich, wenn Sie like zulassen könnten

private _employeeIdList : Zahl[]

MitarbeiterIDListe abrufen(): Zahl[] {
gib this._employeeIdList zurück;
}
set EmployeeIDList(_idList: beliebig ) {
if (typeof _idList== 'string') {
this._employeeIdList = _idList.split(',').map(d => Number(d));
}
sonst if (typeof _idList== 'object') {
this._employeeIdList = _idList as number[];
}
}

Ich hätte dieses Problem leicht gelöst und es ist vollkommen typsicher, obwohl Sie einen anderen Typ in SET zulassen, aber es hindert uns immer noch daran, der Eigenschaft einen falschen Typ zuzuweisen. Also gewinnen, gewinnen. Ich hoffe, die Teammitglieder werden ihr Ego niederlegen und versuchen, das Problem, das dadurch entsteht, zu überdenken und es zu beheben.

Ich möchte anmerken, dass ich dies immer noch für sehr wichtig halte, um das Verhalten bestehender JS-Bibliotheken zu modellieren.

Während es schön und gut ist, die Nase hochzudrehen und einen Setter zu definieren, der einen Typ in eine Teilmenge des eingehenden Typs zwingt und diese Teilmenge im Getter immer als Codegeruch zurückgibt, ist dies in Wirklichkeit ein ziemlich verbreitetes Muster im JS-Land .

Ich finde TypeScript insofern schön, als es uns ermöglicht, sehr ausdrucksstark zu sein, wenn wir die Kadaver bestehender JS-APIs beschreiben, selbst wenn sie keine ideal strukturierten APIs haben. Ich denke, dies ist ein Szenario, in dem, wenn TypeScript ausdrucksstark genug wäre, um anzugeben, dass der Getter immer einen Typ zurückgeben würde, der die strikte Teilmenge des Typs des Getters ist, dies einen enormen Mehrwert bei der Modellierung vorhandener APIs schaffen würde, sogar die DOM!

Trusted Types ist ein neuer Browser-API-Vorschlag zur Bekämpfung von DOM XSS. Es ist bereits in Chromium implementiert (hinter einem Flag). Der Großteil der API modifiziert Setter verschiedener DOM-Eigenschaften, um vertrauenswürdige Typen zu akzeptieren. Beispielsweise akzeptiert $ .innerHTML TrustedHTML | string , während es immer string zurückgibt. Um die API in TypeScript zu beschreiben, müsste dieses Problem behoben werden.

Der Unterschied zu den vorherigen Kommentaren besteht darin, dass dies eine Browser-API (und keine Benutzerlandbibliothek) ist, die nicht einfach geändert werden kann. Auch die Auswirkung der Änderung des Typs von Element.innerHTML in any (was die einzig mögliche Lösung ist) ist größer als die ungenaue Beschreibung einer User-Land-Bibliothek.

Besteht die Möglichkeit, dass dieser Pull-Request erneut geöffnet wird? Oder gibt es andere Lösungen, die ich übersehen habe?

Cc: @mprobst , @koto.

In einer Sprache, die Type Union unterstützt, wie TypeScript, ist diese Funktion natürlich und ein Verkaufsargument.

@RyanCavanaugh selbst wenn Getter und Setter denselben Typ haben, ist nicht garantiert, dass o.x === y nach o.x = y da der Setter vor dem Speichern des Werts eine Bereinigung vornehmen kann.

element.scrollTop = -100;
element.scrollTop; // returns 0

Ich unterstütze die Bedenken von @vrana. Das aktuelle Verhalten macht es unmöglich, einige der vorhandenen APIs in Typescript zu modellieren.

Dies gilt insbesondere für Web-APIs, bei denen die Setter- und Getter-Typen für viele unterschiedlich sind. In der Praxis führen Web-API-Setter alle Arten von Typumwandlungen durch, von denen einige direkt für eine bestimmte Browserfunktion spezifiziert sind, die meisten jedoch implizit über IDL . Viele der Setter ändern den Wert auch, siehe zB Location Interface Spec. Dies ist kein Fehler eines einzelnen Entwicklers, sondern eine Spezifikation der API, gegen die die Webentwickler codieren.

Durch Eingrenzen der Getter-Typen kann Typescript diese APIs darstellen, was jetzt unmöglich ist.

Sie können diese APIs darstellen, weil es richtig ist zu sagen, dass der Typ der Eigenschaft die Vereinigung der möglichen Typen ist, die Sie dem Setter bereitstellen oder vom Getter erhalten können.

Es ist einfach nicht _effizient_, eine API auf diese Weise zu beschreiben. Sie verlangen vom Verbraucher, dass er in jedem Fall, in dem er den Getter verwendet, um die möglichen Typen einzugrenzen, einen Typwächter verwendet.

Das ist in Ordnung, wenn Sie es nicht unbedingt gemacht haben, dass Sie immer einen eingeschränkten Typ von einem Getter zurückgeben, da viele Web-APIs sogar in ihrer Spezifikation gesperrt sind.

Aber selbst wenn wir das für einen Moment beiseite lassen und über Benutzer-APIs sprechen, ist ein starker Anwendungsfall für das Akzeptieren von Union-Typen auf einem Setter das "Skript". Wir möchten eine Reihe diskreter Typen akzeptieren, die wir akzeptabel zu dem Typ zwingen können, den wir tatsächlich wollen.

Warum das zulassen? Benutzerfreundlichkeit.

Das spielt für APIs, die Sie intern für die Verwendung durch Ihr eigenes Team entwickeln, möglicherweise keine Rolle, kann aber für APIs, die für den öffentlichen, allgemeinen Gebrauch konzipiert sind, sehr wichtig sein.

Es ist schön, dass TypeScript es uns ermöglichen kann, eine API genau zu beschreiben, die die für einige ihrer Eigenschaften akzeptablen Typen gelockert hat, aber dieser Vorteil wird durch die Reibung am anderen Ende beeinträchtigt, wo eine übermäßige Typprüfung/-überwachung erforderlich ist, um den Getter-Rückgabetyp zu bestimmen , die wir lieber _angeben_ würden.

Ich würde argumentieren, dass dies ein anderes Szenario ist als der idealisierte Fall, den @RyanCavanaugh postuliert. Dieser Fall impliziert, dass Sie Getter und Setter immer denselben Union-Typ haben sollten, da Ihr Hintergrundfeld auch denselben Union-Typ hat, und Sie diesen Wert immer nur umrunden, und es ist einfach unsinnig, den Typ zu ändern.

Ich denke, dieser Fall dreht sich um eine idealisiertere Verwendung von Union-Typen, wobei Sie sich mit konstruierten Typen befassen und diesen Union-Typ wirklich als semantische Einheit behandeln, für die Sie wahrscheinlich einen Alias ​​hätten erstellen sollen.

type urlRep = string | Url;

Die meisten Dinge werden es einfach umrunden und sich nur mit gewöhnlichen Requisiten befassen, und in einigen Fällen werden Sie die Blackbox mit einigen Typenwächtern brechen.

Ich würde behaupten, dass dies ein völlig anderes Szenario ist als das, was wir hier beschreiben. Was wir beschreiben, ist die Realität, dass allgemeine/öffentliche Verwendungs-APIs, insbesondere für die Verwendung in Skriptsprachen wie JavaScript, oft absichtlich die akzeptablen Typen für einen Setter lockern, weil es eine Reihe von Typen gibt, denen sie zustimmen, den idealen Typ zu erzwingen Sie bieten es als Verbesserung der Lebensqualität an, all dies zu akzeptieren.

So etwas mag unsinnig erscheinen, wenn Sie sowohl Produzent als auch Konsument einer API sind, da es nur mehr Arbeit bedeutet, kann aber sehr sinnvoll sein, wenn Sie eine API für den Massenverbrauch entwerfen.

Und ich glaube nicht, dass irgendjemand jemals wollen würde, dass der Setter/Getter _disjunkte_ Typen hat. Was hier diskutiert wird, ist, dass der API-Erzeuger dem API-Verbraucher zusichert, dass der Getter einen Wert mit einem Typ zurückgibt, der eine strikte Teilmenge des Vereinigungstyps des Setters ist.

Und ich glaube nicht, dass irgendjemand jemals wollen würde, dass der Setter / Getter disjunkte Typen hat.

Nur um dem mal eine Gegenansicht zu geben. Ich würde das auf jeden Fall wollen, weil es in Javascript völlig legal ist, disjunkte Typen für Setter/Getter zu haben. Und ich denke, das Hauptziel sollte es sein, das Typsystem ausdrucksstark genug zu machen, um alles, was in Javascript legal ist, korrekt einzugeben (zumindest auf der .d.ts-Ebene). Ich verstehe, dass es keine gute Praxis ist, auch nicht zum Beispiel globale Objekte oder das Ändern des Prototyps von eingebauten Funktionen wie Funktionen usw., aber wir können diese immer noch gut in .d.ts-Dateien eingeben, und ich habe noch nie gehört, dass jemand das bedauert wir können das tun (obwohl es dazu führt, dass einige schlecht gestaltete Typdefinitionsdateien auf definitiv typisiert erscheinen).

Ich bin gerade auf eine Situation gestoßen, in der ich dies als Feature benötige, um die richtigen Typen zu haben, die ich nicht kontrolliere. Der Setter muss liberaler sein und zu einem konservativeren Typ zwingen, der vom Getter konsequent zurückgegeben werden kann.

Ich wünschte wirklich, diese Funktion gäbe es. Ich portiere JavaScript-Code nach TypeScript, und es ist wirklich schade, dass ich die Setter umgestalten muss, wenn ich Typsicherheit haben möchte.

Zum Beispiel habe ich so etwas:

class Vector3 { /* ... */ }

type XYZ = Vector3 | [number, number, number] | {x: number, y: number, z: number}
type PropertyAnimator = (x: number, y: number, z: number, timestamp: number) => XYZ
type XYZSettables =  XYZ | PropertyAnimator

export class Transformable {
        // ...

        set position(newValue: XYZSettables) {
            this._setPropertyXYZ('position', newValue)
        }
        get position(): Vector3 {
            return this._props.position
        }

        // ...
}

Und wie Sie sich vorstellen können, ist die Nutzung sehr flexibel:

const transform = new Transformable

// use an array
transform.position = [20, 30, 40]

// use an object
transform.position = {y: 30, z: 40} // skip `x` this time

// animate manually, a property directly
requestAnimationFrame((time) => {
  transform.position.x = 100 * Math.sin(time * 0.001)
})

// animate manually, with an array, which could be shared across instances
const pos = [10, 20, 30]
requestAnimationFrame((time) => {
  pos[2] = 100 * Math.sin(time * 0.001)
  transform.position = pos
})

// Animate with a property function
transform.position = (x, y, z, time) => [x, y, 100 * Math.sin(time * 0.001)]

// or a simple increment:
transform.position = (x, y, z) => [x, y, ++z]

// etc

// etc

// etc

Das ist 4 Jahre alt. Wie wird es NOCH nicht behoben? Diese Funktion ist, wie in den obigen Kommentaren erwähnt, von Bedeutung! Erwägen Sie zumindest, das Thema erneut zu eröffnen?

Und ich stimme @kitsonk vollkommen zu:

@kitsonk Ich denke, in den obigen Kommentaren wurden mehr als genug überzeugende Anwendungsfälle bereitgestellt. Während das aktuelle Design mit dieser Einschränkung einem gemeinsamen Muster der meisten anderen typisierten Sprachen folgt, ist es im Zusammenhang mit Javascript unnötig und übermäßig restriktiv. Während es beabsichtigt ist, ist das Design falsch.

In TypeScript 3.6 wurde eine Syntax zum Eingeben von Accessoren in Deklarationsdateien eingeführt , wobei die Einschränkung beibehalten wurde, dass der Typ von Getter und Setter identisch sein muss.

Unser UI-Toolkit stützt sich stark auf die Auto-Casting-Setter, wobei der Setter mehr Typen akzeptiert als der einzelne Typ, der vom Getter zurückgegeben wird. Es wäre also schön, wenn TS eine Möglichkeit anbieten würde, dieses Muster typsicher zu machen.

Es sieht so aus, als hätte @RyanCavanaugh vor 3 Jahren die konkreteste Widerlegung dieser Funktion geliefert. Ich frage mich, ob die kürzlich gemeldeten DOM-Anwendungsfälle sowie die Verfügbarkeit einer neuen Deklarationssyntax eine neue Entscheidung zulassen könnten?

Ja, sobald ich dieses neue Feature gesehen habe, dachte ich auch sofort an diese Feature-Anfrage. Ich möchte es auch für die Zwecke unserer UI-Frameworks. Das ist ein echter Anwendungsfall, Leute.

Die TSConf 2019 findet am 11. Oktober statt und ich denke, dies wäre eine großartige Rückschau in der Q&A-Sitzung, wenn jemand die Chance dazu hätte 🤔

Ich habe das vielleicht schon einmal in diesem langen Thread gesagt, aber ich denke, es verdient eine Wiederholung.

Meiner Meinung nach dient das ausdrucksstarke Typsystem in TypeScript zwei getrennten Zwecken. Die erste ermöglicht es Ihnen, sichereren neuen Code zu schreiben. Und wenn dies der einzige Zweck wäre, könnten Sie vielleicht argumentieren, dass Sie diesen Anwendungsfall vermeiden könnten, da Code möglicherweise sicherer ist, wenn Sie dieses Szenario verbieten würden (aber ich denke, wir könnten immer noch darüber streiten).

Der zweite Zweck besteht jedoch darin, das Verhalten von Bibliotheken so zu erfassen, wie sie sind, und es ist eine ziemlich gängige Praxis in DOM- und JS-Bibliotheken, den Setter automatisch zu erzwingen, aber einen erwarteten Typ im Getter zurückzugeben. Wenn wir dies im Typsystem erfassen können, können wir vorhandene Frameworks genauer so beschreiben, wie sie sind .

Zumindest denke ich, dass Design Limitation hier entfernt werden könnte, ich glaube nicht, dass das mehr zutrifft.

Dies ist offenbar der Grund dafür, dass .style.display = ...something nullable... per #33749 keine Typechecks mehr durchführt; was als ein Problem der Nullablity-Korrektheit dargestellt wird. Das ist ein bisschen unaufrichtig, nicht wahr? Es ist ärgerlich, neue Breaking Changes herausfinden zu müssen, wenn sie fälschlicherweise als Bugfixes bezeichnet werden (ich habe nach tatsächlichen Problemen gesucht, die dies möglicherweise verursacht hat). Ich persönlich finde es viel weniger überraschend, dass null das spezielle Verhalten "use default" hat, als das der leere String tut; und bis Typescipt 3.7 habe ich es vorgezogen, null zu verwenden, um das zu erfassen. In jedem Fall wäre es schön, wenn absichtlich falsche Typanmerkungen, die zur Umgehung dieser Einschränkung vorgenommen wurden, eindeutig als solche gekennzeichnet würden, um Zeit bei der Prüfung von Upgrade-Problemen zu sparen.

Ich bin auch daran interessiert, dafür einen Weg nach vorne zu finden. Was wäre, wenn es nur in Umgebungskontexten erlaubt wäre? @RyanCavanaugh würde das helfen, Ihre Bedenken hinsichtlich der Komplexität auszuräumen?

Mein Anwendungsfall dafür ist, dass ich eine API habe, bei der ein Proxy ein Versprechen zurückgibt, aber eine Set-Operation kein Versprechen setzt. Ich kann das im Moment nicht in TypeScript beschreiben.

let post = await loadPost()
let user = await loadUser()
post.author = user // Proxy handles links these two objects via remote IDs
await save(post)

// Somewhere else in code
let post = await loadPost()
let author = await post.author

Unabhängig davon, ob es sich um die Typkorrektur nativer Funktionen oder Proxys im Allgemeinen handelt, scheint TypeScript der Ansicht zu sein, dass diese Arten von Funktionen ein Fehler waren.

Sie sollten wirklich #6 und #7 von dieser Liste streichen (https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals#goals)

Mein Problem

Ich möchte ein Element in ein Array in der Set-Methode verschieben und das gesamte Array in der Get-Methode abrufen. Im Moment muss ich set myArray(...value: string[]) verwenden, was gut funktioniert. Habe viel mit diesem Problem zu tun ... bitte ziehen Sie es in Betracht, dies zu entfernen. Eine Info oder Warnung würde dafür gut funktionieren.

Beispiel (was ich gerne machen würde)

class MyClass {
   _myArray: string[] = [];

   set myArray(value: string) {
      this._myArray.push(value);
   }

   get myArray(): string[] {
      return this._myArray;
   }
}

Beispiel (was ich tun muss)

class MyClass {
   _myArray: string[] = [];

   set myArray(...value: string[]) {
      this._myArray.push(value[0]);
   }

   get myArray(): string[] {
      return this._myArray;
   }
}

Eine Lösung, die mir einfiel, als ich dies brauchte, bestand darin, die Eigenschaft auf union type zu setzen. Dies liegt daran, dass meine Zeit als Zeichenfolge von der API eingeht, ich sie aber als Moment-Objekt für meine Benutzeroberfläche festlegen muss.

Mein Beispiel

class TimeStorage {
    _startDate: string | Moment = ""
    _endDate: string | Moment = ""

    set startDate(date: Moment | string) {
        this._startDate = moment(date).utc()
    }
    get startDate(): Moment | string {
        return moment.utc(this._startDate)
    }

    set endDate(date: Moment) { 
        this._endDate = moment.utc(date)
    }
    get endDate() { 
        return moment.utc(this._endDate)
    }
}

Ich bin etwas spät zur Party, aber ich bin kürzlich selbst auf dieses Problem gestoßen. Es ist ein interessantes Dilemma, also habe ich ein bisschen damit herumgespielt.

Nehmen wir an, das ist etwas, was wir machen wollen, eine einfache Klasse, die eine Eigenschaft hat, die Zeichenfolgen und Zahlen akzeptiert (für welchen Zweck auch immer), aber die Eigenschaft wird wirklich als Zeichenfolge gespeichert:

class Foo {
    constructor() {
        this._bar = '';
        this.baz = '';
    }

    // error: 'get' and 'set' accessor must have the same type.(2380)
    get bar(): string {
        return this._bar;
    }

    // error: 'get' and 'set' accessor must have the same type.(2380)
    set bar(value: string | number) {
        this._bar = value.toString();
    }

    public baz: string;
    private _bar: string;
}

Da dies nicht möglich ist und wir dennoch eine starke Typisierung wünschen, benötigen wir zusätzlichen Code. Lassen Sie uns nun für unser Beispiel so tun, als hätte jemand eine Eigenschaftsbibliothek erstellt, die so aussah:

/**
 * Abstract base class for properties.
 */
abstract class Property<GetType, SetType> {
    abstract get(): GetType;
    abstract set(value: SetType): void;
}

/**
 * Proxify an object so that it's get and set accessors are proxied to
 * the corresponding Property `get` and `set` calls.
 */
function proxify<T extends object>(obj: T) {
    return new Proxy<any>(obj, {
        get(target, key) {
            const prop = target[key];
            return (prop instanceof Property) ? prop.get() : prop;
        },
        set(target, key, value) {
            const prop = target[key];
            if (prop instanceof Property) {
                prop.set(value);
            } else {
                target[key] = value;
            }
            return true;
        }
    });
}

Mit dieser Bibliothek könnten wir unsere typisierte Eigenschaft für unsere Klasse wie folgt implementieren:

class Bar extends Property<string, string | number> {
    constructor(bar: string | number) {
        super();
        this.set(bar);
    }

    get(): string {
        return this._bar;
    }

    set(value: string | number) {
        this._bar = value.toString();
    }

    private _bar: string;
}

class Foo {
    constructor() {
        this.bar = new Bar('');
        this.baz = '';
    }

    public bar: Bar;
    public baz: string;
}

Und dann hätte die Verwendung dieser Klasse einen typsicheren Getter und Setter für bar :

const foo = new Foo();

// use property's typed setter
foo.bar.set(42);
foo.baz = 'foobar';

// use property's typed getter
// output: 42 foobar
console.log(foo.bar.get(), foo.baz);

Und wenn Sie die Setter oder Getter für Eigenschaften dynamischer verwenden müssen, können Sie die Funktion proxify verwenden, um sie zu umschließen:

const foo = new Foo();

// use proxified property setters
Object.assign(proxify(foo), { bar: 100, baz: 'hello world' });

// use property's typed getter
// output: 100 hello world
console.log(foo.bar.get(), foo.baz);

// use proxified property getters
// output: {"bar":"100","baz":"hello world"}
console.log(JSON.stringify(proxify(foo)));

Haftungsausschluss: Wenn jemand diesen Code nehmen und in eine Bibliothek stellen oder damit tun möchte, was immer er/sie möchte, kann er dies gerne tun. Ich bin zu faul.

Nur ein paar zusätzliche Notizen und dann verspreche ich, dass ich aufhöre, diese lange Liste von Leuten zu spammen!

Wenn wir dies der imaginären Bibliothek hinzufügen:

/**
 * Create a property with custom `get` and `set` accessors.
 */
function property<GetType, SetType>(property: {
    get: () => GetType,
    set: (value: SetType) => void
}) {
    const obj = { ...property };
    Object.setPrototypeOf(obj, Property.prototype);
    return obj;
}

Wir erhalten eine Implementierung, die der ursprünglichen Klasse etwas näher kommt und vor allem Zugriff auf this hat:

class Foo {
    constructor() {
        this._bar = '';
        this.baz = '';
    }

    public bar = property({
        get: (): string => {
            return this._bar;
        },
        set: (value: string | number) => {
            this._bar = value.toString();
        }
    });

    public baz: string;
    private _bar: string;
}

Es ist nicht das Schönste auf der Welt, aber es funktioniert. Vielleicht kann jemand das verfeinern und etwas machen, das wirklich schön ist.

Für den unwahrscheinlichen Fall, dass jemand, der diesen Thread liest, nicht davon überzeugt ist, dass diese Funktion wichtig ist, gebe ich Ihnen diesen unglaublich hässlichen Hack, den Angular gerade eingeführt hat :

Es wäre ideal, hier den Typ des Werts von boolean in boolean|'' zu ändern, damit er mit der Menge von Werten übereinstimmt, die tatsächlich vom Setter akzeptiert werden. TypeScript erfordert, dass sowohl der Getter als auch der Setter denselben Typ haben. Wenn der Getter also einen booleschen Wert zurückgeben sollte, bleibt der Setter beim schmaleren Typ hängen ... Angular unterstützt die Überprüfung eines breiteren, freizügigeren Typs für @Input() als es ist für das Eingabefeld selbst deklariert. Aktivieren Sie dies, indem Sie der Komponentenklasse eine statische Eigenschaft mit dem Präfix ngAcceptInputType_ hinzufügen:

````
Klasse SubmitButton {
private _disabled: boolean;

deaktiviert werden (): boolean {
gib das zurück._disabled;
}

deaktiviert setzen (Wert: boolean) {
this._disabled = (Wert === '') || Wert;
}

statisch ngAcceptInputType_disabled: boolean|'';
}
````

Bitte, bitte beheben Sie das.

Ja, wir haben buchstäblich überall diese Hässlichkeit, um die Muster zu unterstützen, die Angular verwenden möchte. Die aktuelle Beschränkung ist zu rechthaberisch.

Wenn TS verwendet werden soll, um die gesamte Breite der Bibliotheken im Webland zu modellieren, muss es sich bemühen, weniger eigensinnig zu sein, oder es wird nicht alle Designs modellieren können.

An diesem Punkt glaube ich, dass die Verantwortung bei den Mitwirkenden liegt, zu kommunizieren, warum dieses Thema nicht wiedereröffnet wird. Gibt es ein beitragendes Mitglied, das an dieser Stelle sogar entschieden dagegen ist?

Wir erstellen eine DOM-Bibliothek für NodeJs, die der W3C-Spezifikation entspricht, aber dieses Typescript-Problem macht dies unmöglich. Irgendein Update?

Es ist schockierend für mich, dass einige im Kernteam von TypeScript gegen eine Funktion sind, die ERFORDERLICH ist, um eine der am häufigsten verwendeten JavaScript-Bibliotheken der Welt neu zu erstellen: das DOM.

Es gibt keine andere Möglichkeit, a) viele Teile der W3C-DOM-Spezifikation zu implementieren und b) das Typsystem von TypeScript zu verwenden (es sei denn, Sie verwenden überall any , was den gesamten Zweck von TypeScript zunichte macht).

Die Homepage von typescriptlang.org ist derzeit ungenau, wenn es heißt:

TypeScript ist eine typisierte Obermenge von JavaScript, die zu einfachem JavaScript kompiliert wird.

Die Unfähigkeit, die DOM-Spezifikation von JavaScript neu zu erstellen, zeigt, dass TypeScript immer noch eine Teilmenge von Javascript ist, KEINE Obermenge .

In Bezug auf die Implementierung dieser Funktion stimme ich @gmurray81 und vielen anderen zu, die argumentiert haben, dass der Getter-Typ eine Teilmenge des Union-Typs eines Setters sein muss. Dies gewährleistet kein unzusammenhängendes Tippen. Dieser Ansatz ermöglicht es einer Setter-Funktion, die Eingabe zu bereinigen, ohne den Getter-Typ zu zerstören (dh gezwungen zu sein, auf die Verwendung any zurückzugreifen).

Hier ist, was ich nicht kann. Etwas Einfaches, das ich in JS ähnlich gemacht habe, aber in TS nicht kann.

4-jähriges Problem, das WIRKLICH umgesetzt werden könnte.

export const enum Conns {
  none = 0, d = 1, u = 2, ud = 3,
  r = 4, rd = 5, ru = 6, rud = 7,
  l = 8, ld = 9, lu = 10, lud = 11,
  lr = 12, lrd = 13, lru = 14, lrud = 15,
  total = 16
}

class  Tile {
  public connections = Conns.none;

  get connLeft() { return this.connections & Conns.l; };

  set connLeft(val: boolean) { this.connections = val ? (this.connections | Conns.l) : (this.connections & ~Conns.l); }
}

Bin jetzt auf dieses Problem gestoßen. Das Folgende scheint eine vernünftige Lösung zu sein, um @calebjclark zu zitieren:

In Bezug auf die Implementierung dieser Funktion stimme ich @gmurray81 und vielen anderen zu, die argumentiert haben, dass der Getter-Typ eine Teilmenge des Union-Typs eines Setters sein muss. Dies gewährleistet kein unzusammenhängendes Tippen. Dieser Ansatz ermöglicht es einer Setter-Funktion, die Eingabe zu bereinigen, ohne den Getter-Typ zu zerstören (dh gezwungen zu sein, auf die Verwendung von Any zurückzugreifen).

Da der Getter eine Teilmenge des Typs des Setters ist, denke ich, dass dies unnötig eingeschränkt ist. Wirklich, der Typ eines Setters _könnte_ theoretisch alles sein, solange die Implementierung immer einen Untertyp des Typs des Getters zurückgibt. Das Erfordernis, dass der Setter ein Supertyp des Getter-Typs sein muss, ist eine seltsame Einschränkung, die die paar in diesem Ticket vorgestellten Anwendungsfälle erfüllen könnte, aber später sicherlich Beschwerden hervorrufen würde.

Davon abgesehen sind Klassen implizit auch Schnittstellen, wobei Getter und Setter im Wesentlichen Implementierungsdetails sind, die in einer Schnittstelle nicht erscheinen würden. Im Beispiel von OP würden Sie type MyClass = { myDate(): moment.Moment; } erhalten. Sie müssten diese neuen Getter- und Setter-Typen irgendwie als Teil der Schnittstelle verfügbar machen, obwohl ich persönlich nicht sehe, warum das wünschenswert wäre.

Dieses Problem verlangt im Grunde nach einer weniger eingeschränkten Version der Operatorüberladung von = und === . (Getter und Setter sind bereits Operatorüberladungen.) Und als jemand, der sich mit Operatorüberladungen in anderen Sprachen befasst hat, möchte ich sagen, dass ich denke, dass sie in den meisten Fällen vermieden werden sollten. Sicher, jemand könnte ein mathematisches Beispiel wie kartesische und polare Punkte vorschlagen, aber in den meisten Fällen, in denen Sie TS verwenden würden (einschließlich der Beispiele in diesem Thread), würde ich argumentieren, dass das Überladen von Operatoren wahrscheinlich ein Codegeruch ist. Es sieht vielleicht so aus, als würde es die API einfacher machen, aber es bewirkt tendenziell das Gegenteil. Wie bereits erwähnt, ist es sehr verwirrend und unintuitiv, wenn das Folgende nicht unbedingt wahr ist.

foo.x = y;
if (foo.x === y) { // this could be false?

(Ich denke, die einzigen Fälle, in denen ich gesehen habe, dass Setter verwendet wurden, die vernünftig erschienen, waren das Protokollieren und das Setzen eines "Dirty" -Flags auf Objekten, damit etwas anderes erkennen kann, ob ein Feld geändert wurde. Keines davon ändert wirklich den Vertrag des Objekt.)

@MikeyBurkman

aber in den meisten Fällen, in denen Sie TS verwenden (einschließlich der Beispiele in diesem Thread), würde ich argumentieren, dass das Überladen von Operatoren wahrscheinlich ein Code-Geruch ist, [ ... ]

 foo.x = y; 
 if (foo.x === y) { // this could be false?

Das bloße Übereinstimmen der Typen verhindert also nicht überraschendes Verhalten, und in der Tat zeigt el.style.display = ''; //so is this now true: el.style.display === ''? , dass das auch nicht theoretisch ist. Selbst bei einfachen alten Feldern gilt die Annahme übrigens nicht für NaN.

Aber was noch wichtiger ist, Ihr Argument ignoriert den Punkt, dass TS nicht wirklich eine Meinung zu diesen Dingen haben kann, weil TS mit bestehenden JS-APIs interagieren muss, einschließlich wichtiger und unwahrscheinlicher Änderungen von Dingen wie dem DOM. Und als solches spielt es einfach keine Rolle, ob die API ideal ist oder nicht; Was zählt, ist, dass TS mit solchen gegebenen APIs nicht sauber interagieren kann. Jetzt sind Sie gezwungen, entweder die Fallback-Logik, die die API intern verwendet hat, neu zu implementieren, um einen an den Setter übergebenen Wert außerhalb des Getter-Typs zu erzwingen, und somit die Eigenschaft als Getter-Typ einzugeben, oder die Typprüfung zum Hinzufügen zu ignorieren nutzlose Typzusicherungen an jeder Getter-Site und geben somit die Eigenschaft als Setter-Typ ein. Oder, schlimmer noch: tun, was auch immer TS will, um für das DOM zu kommentieren, unabhängig davon, ob das ideal ist.

All diese Entscheidungen sind schlecht. Der einzige Weg, dies zu vermeiden, besteht darin, die Notwendigkeit zu akzeptieren, JS-APIs so originalgetreu wie möglich darzustellen, und hier: das scheint möglich (zugegebenermaßen ohne Kenntnis der TS-Interna, die dies möglicherweise entschieden nicht trivial macht).

das scheint möglich (zugegebenermaßen ohne Kenntnis der TS-Interna, die dies entschieden nicht trivial machen könnte)

Ich vermute, dass einige der neuen Typdeklarationssyntax, die sie kürzlich aktiviert haben, dies ausdrückbar machen könnten, wenn es vorher weniger machbar war, also sollten sie wirklich in Betracht ziehen, dieses Problem erneut zu öffnen ... @RyanCavanaugh

In einer idealen Welt würde TS mit allen JS-APIs interagieren. Aber das ist nicht der Fall. Es gibt eine Reihe von JS-Redewendungen, die in TS nicht korrekt eingegeben werden können. Ich möchte jedoch, dass sie sich darauf konzentrieren, Dinge zu beheben, die tatsächlich zu besseren Programmiersprachen führen, nicht zu schlechteren. Während es nett wäre, dies auf vernünftige Weise zu unterstützen, gibt es ein Dutzend anderer Dinge, für die ich persönlich lieber wäre, dass sie zuerst ihre begrenzte Zeit verbringen. Das ist ganz unten auf dieser Liste.

@MikeyBurkman die Prioritätsfrage ist gültig. Mein Hauptanliegen ist die Entscheidung von @RyanCavanaugh , dieses Problem zu schließen und abzuschreiben. Das Ablehnen aller in diesem Thread aufgezeigten Probleme steht in direktem Konflikt mit der erklärten Mission von Typescript, die eine "Obermenge" von Javascript sein soll (anstelle einer Teilmenge).

Ja, ich kann definitiv zustimmen, dass dieses Problem wahrscheinlich nicht geschlossen werden sollte, da es etwas ist, das _wahrscheinlich_ irgendwann behoben werden sollte. (Obwohl ich wirklich bezweifle, dass es so sein wird.)

Während es nett wäre, dies auf vernünftige Weise zu unterstützen, gibt es ein Dutzend anderer Dinge, für die ich persönlich lieber wäre, dass sie zuerst ihre begrenzte Zeit verbringen

Ich denke, Sie unterbieten einen wichtigen Aspekt von TypeScript, da es die Verwendung der vorhandenen DOM-APIs und vorhandenen JS-APIs sicherer machen soll. Es existiert, um mehr als nur Ihren eigenen Code in seiner Blase sicher aufzubewahren.

Hier ist mein Standpunkt, ich baue Komponentenbibliotheken, und obwohl ich angesichts meiner Druther nicht unbedingt absichtlich dazu führen würde, dass diese Diskrepanz zwischen Settern/Gettern besteht. Manchmal wird meine Hand gezwungen, je nachdem, wie meine Bibliotheken mit anderen Systemen vor Ort kommunizieren müssen. Beispielsweise legt Angular alle Eingabeeigenschaften einer Komponente, die aus dem Markup stammt, als Zeichenfolgen fest, anstatt eine Typumwandlung basierend auf ihrem Wissen über den Zieltyp durchzuführen (zumindest habe ich es zuletzt überprüft). Weigern Sie sich also, Strings zu akzeptieren? Machen Sie alles zu einer Zeichenfolge, obwohl dies Ihre Typen schrecklich machen würde? Oder tun Sie, was TypeScript von Ihnen verlangt, und verwenden Sie einen Typ wie: string | Farbe, aber machen Sie die Verwendung der Getter schrecklich. Dies sind alles ziemlich schreckliche Optionen, die die Sicherheit verringern, wenn etwas zusätzliche Ausdruckskraft des Typsystems geholfen hätte.

Das Problem ist, dass nicht nur Angular diese Probleme verursacht. Angular geriet in diese Situation, weil es viele Szenarien im DOM widerspiegelt, in denen Auto-Concercion auf einem Eigenschaftssatz stattfindet, aber der Getter immer ein erwarteter singulärer Typ ist.

Schauen Sie ein bisschen nach oben: Angular ist viel schlimmer als Sie denken .

Für mich ist es normalerweise das Problem, wenn Sie einen Wrapper um einen Allzweckspeicher erstellen möchten, der Schlüssel-Wert-Paare speichern kann, und Sie Benutzer auf bestimmte Schlüssel beschränken möchten.

class Store {
  private dict: Map<string, any>;

  get name(): string | null {
    return this.dict.get('name') as string | null;
  }

  set name(value: string) {
    this.dict.set('name', value);
  }
}

Sie möchten eine solche Einschränkung haben: Der Benutzer kann null erhalten, wenn der Wert zuvor nicht festgelegt wurde, aber er kann ihn nicht auf null setzen. Derzeit ist dies nicht möglich, da der Setter-Eingabetyp null enthalten muss.

@fan-tom, großartiger Anwendungsfall dafür, warum dieses Problem erneut geöffnet werden muss! Lass sie kommen.

Diese Ausgabe hat eine extrem hohe Anzahl an positiven Stimmen!

Dies ist das Projekt des TS-Teams, also können sie tun, was sie für richtig halten, wie es ihnen Spaß macht. Aber wenn sie darauf abzielen, dies für eine externe Community von Benutzern so nützlich wie möglich zu machen, dann hoffe ich, dass das TS-Team die hohe Anzahl von Stimmen der Community stark berücksichtigen kann.

Die Homepage von typescriptlang.org ist derzeit ungenau, wenn es heißt:

TypeScript ist eine typisierte Obermenge von JavaScript, die zu einfachem JavaScript kompiliert wird.

TypeScript ist eine typisierte _Obermenge_ einer _Untermenge_ von JavaScript, die zu einfachem JavaScript kompiliert wird. :smiley:

TypeScript ist eine typisierte Obermenge einer Teilmenge von JavaScript, die zu einfachem JavaScript kompiliert wird.

Etwas diplomatischer könnte man sagen, dass es sich um eine eigensinnige Obermenge von JavaScript handelt, wenn das Team diese Art von eigensinniger Auslassung aus der Typsystemmodellierung vornimmt.

Sie sagen: "Wir werden das nicht modellieren, weil es schwer für uns ist, und wir denken, dass Sie es sowieso nicht tun sollten." Aber JavaScript ist randvoll mit Dingen, die Sie nicht tun _sollten_, und im Allgemeinen wird Typescript Sie nicht daran hindern, diese Dinge zu tun, wenn Sie den Willen und das Know-how haben, da es so aussieht, als ob die allgemeine Strategie darin besteht, keine Meinung darüber zu haben, was JavaScript ist Sie können und können nicht einfach als Typescript ausgeführt werden.

Aus diesem Grund ist es so seltsam, sich zu weigern, dieses gängige (im DOM!) Szenario zu modellieren, indem man die Überzeugung anführt, dass APIs keine Setter-basierte Zwangsmaßnahmen durchführen sollten, um dies nicht zu tun.

Derzeit scheint dies nicht möglich zu sein, und ich muss auf Folgendes zurückgreifen:

class MyClass {

    private _myDate: moment.Moment;

    get myDate(): moment.Moment {
        return this._myDate;
    }

    set myDate(value: moment.Moment) {
        assert.fail('Setter for myDate is not available. Please use: setMyDate() instead');
    }

    setMyDate(value: Date | moment.Moment) {
        this._myDate = moment(value);
    }
}

Das Beste, was Sie tun können, ist, einen Konstruktor hinzuzufügen und dort die benutzerdefinierte Setter-Funktion aufzurufen, wenn dies das einzige Mal ist, dass Sie diesen Wert festlegen.

constructor(value: Date | moment.Moment) {
    this.setMyDate(value);
}

Das Problem bei der direkten Wertzuweisung bleibt bestehen.

Irgendwelche Neuigkeiten dazu, nach über 5,5 Jahren?

@xhliu Wie die Beschriftungen andeuten, handelt es sich um eine Designbeschränkung, die zu komplex ist, um angegangen zu werden, und daher ist das Problem geschlossen. Ich würde kein Update erwarten. Die Zeitdauer für ein geschlossenes Problem ist irgendwie irrelevant.

Bitte wieder öffnen. Auch dies muss auf Schnittstellenebene hinzugefügt werden. Ein klassisches Beispiel ist die Implementierung eines Proxys, bei dem Sie volle Flexibilität darüber haben, was Sie lesen und schreiben können.

@xhliu ... zu komplex, um es anzugehen ...

https://ts-ast-viewer.com/#code/MYewdgzgLgBFCm0DyAjAVjAvDA3gKBkJgDMQQAuGAIhQEMAnKvAXzzwWXQDpSQg

const testObj = {
    foo: "bar"
}

testObj.foo

Das Symbol foo hat folgendes zu sagen:

  foo
    flags: 4
    escapedName:"foo"
    declarations: [
      PropertyAssignment (foo)
    ]
    valueDeclaration: PropertyAssignment (foo)

Hier ist viel Platz für zusätzliche Informationen, was für TS ein großartiges Design ist. Wer auch immer dies entwickelt hat, hat dies wahrscheinlich speziell getan, um in Zukunft zusätzliche Funktionen (auch große) zu ermöglichen.

Ab jetzt spekulativ:

Wenn es möglich wäre, (für ein Pseudo-Code-Beispiel) ein accessDelclaration: AccessSpecification (foo) zu deklarieren, dann könnte das PropertyAccessExpression , das das Symbol foo und seine Deklarationen kennt, bedingt prüfen, ob es eine "accessDeclaration" gibt und Verwenden Sie stattdessen die Eingabe daraus.

Unter der Annahme, dass die Syntax vorhanden ist, um diese accessDelclaration -Eigenschaft zum „foo“-Symbol hinzuzufügen, sollte der PropertyAccessExpression in der Lage sein, die „AccessSpecification“ aus dem Symbol abzurufen, das er von ts.createIdentifier("foo") erhält, und unterschiedliche Typen liefern.

ts.createPropertyAccess(
  ts.createIdentifier("testObj"),
  ts.createIdentifier("foo")
)

Spekulativ scheint es, dass der schwierigste Teil dieser Herausforderung wahrscheinlich die Menge an Bike-Shedding in Bezug auf die Syntax (oder vielleicht eine Firmenphilosophie?) wäre, aber aus einer Implementierungsperspektive sollten alle Tools vorhanden sein. Der Funktion ts.createPropertyAccess() würde eine einzelne Bedingung hinzugefügt, und dem Symbol der Objekteigenschaft muss eine Deklarationsklasse zur Darstellung dieser Bedingung und ihrer Auswirkungen hinzugefügt werden.

Es wurden viele gute Beispiele geschrieben, warum dies dringend benötigt wird (insbesondere für DOM und Angular).

Ich möchte nur hinzufügen, dass ich heute davon betroffen war, als ich alten JS-Code zu TS migrierte, wo die Zuweisung string zu window.location nicht funktionierte und ich eine Problemumgehung as any durchführen musste 😟

Die schreibgeschützte Eigenschaft Window.location gibt ein Location-Objekt mit Informationen über den aktuellen Speicherort des Dokuments zurück.

Obwohl Window.location ein schreibgeschütztes Location-Objekt ist, können Sie ihm auch einen DOMString zuweisen. Das bedeutet, dass Sie mit dem Standort in den meisten Fällen so arbeiten können, als wäre er ein String: location = ' http://www.example.com ' ist ein Synonym von location.href = ' http://www.example.com ' .
Quelle

Migration von altem JS-Code zu TS, wo die Zuweisung string zu window.location nicht funktionierte und ich eine as any Problemumgehung durchführen musste

Das ist ein großartiges Beispiel.

TS braucht das. Dies ist ein ganz normaler Teil von JavaScript.

Ich sehe, dass das aktuelle Problem geschlossen wurde. Aber wie ich es verstehe, wurde diese Funktionalität nicht realisiert?

@AGluk , dieses Problem muss erneut geöffnet werden.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

blendsdk picture blendsdk  ·  3Kommentare

Antony-Jones picture Antony-Jones  ·  3Kommentare

wmaurer picture wmaurer  ·  3Kommentare

weswigham picture weswigham  ·  3Kommentare

bgrieder picture bgrieder  ·  3Kommentare