Backbone: Backbone- und ES6-Klassen

Erstellt am 7. Apr. 2015  ·  63Kommentare  ·  Quelle: jashkenas/backbone

Mit den letzten Änderungen an der ES6-Klassenspezifikation (Details hier ) ist es nicht mehr möglich, ES6-Klassen mit Backbone zu verwenden, ohne wesentliche Kompromisse in Bezug auf die Syntax einzugehen. Ich habe eine vollständige Beschreibung der Situation geschrieben hier (stellen Sie sicher , für eine zusätzliche mildernde Option unten auf die Ausführungen zu durchklicken), aber im Wesentlichen gibt es keine Möglichkeit Eigenschaften auf eine Instanz einer Unterklasse hinzufügen vor den Subklassen Eltern Konstruktor ausgeführt wird.

Also das:

class DocumentRow extends Backbone.View {

    constructor() {
        this.tagName =  "li";
        this.className = "document-row";
        this.events = {
            "click .icon":          "open",
            "click .button.edit":   "openEditDialog",
            "click .button.delete": "destroy"
        };
        super();
    }

    initialize() {
        this.listenTo(this.model, "change", this.render);
    }

    render() {
        //...
    }
}

ist in der endgültigen ES6-Spezifikation nicht mehr gültig. Stattdessen haben Sie effektiv 3 (nicht sehr ansprechende) Optionen, wenn Sie versuchen möchten, dass dies funktioniert:

Alle Eigenschaften als Funktionen anhängen

Backbone erlaubt dies, aber es fühlt sich dumm an, so etwas zu schreiben:

class DocumentRow extends Backbone.View {

    tagName() { return "li"; }

    className() { return "document-row";}

    events() {
        return {
            "click .icon":          "open",
            "click .button.edit":   "openEditDialog",
            "click .button.delete": "destroy"
        };
    }

    initialize() {
        this.listenTo(this.model, "change", this.render);
    }

    render() {
        //...
    }
}

im Vergleich zur aktuellen erweiterten Syntax

Führen Sie den Konstruktor zweimal aus

Ich betrachte dies nicht als echte Option, da dies zu Problemen führen würde, die ein zweites Mal mit anderen CIDs ausgeführt werden usw.

Übergeben Sie alle Eigenschaften als Standardoptionen an den Superklassenkonstruktor

Dies wurde von einem Kommentator auf meinem Blog vorgeschlagen und ist wahrscheinlich die derzeit praktischste Option. Es sieht ungefähr so ​​aus:

class MyView extends Backbone.View {
  constructor(options) {
    _.defaults(options, {
      // These options are assigned to the instance by Backbone
      tagName: 'li',
      className: 'document-row',
      events: {
        "click .icon": "open",
        "click .button.edit": "openEditDialog",
        "click .button.delete": "destroy"
      },
      // This option I'll have to assign to the instance myself
      foo: 'bar'
    });


    super(options);


    this.foo = options.foo;
  }
}

Da alle diese aktuellen Optionen klare Kompromisse gegenüber der aktuellen Backbone-Extended-Syntax beinhalten, wäre es schön, wenn eine bessere Lösung entwickelt werden könnte. Ich bin mir nicht ganz sicher, wie das aussehen soll, aber eine Idee, die mir während des Schreibens für meinen Blog in den Sinn kam, war das Hinzufügen einer "Eigenschaften" -Funktion, die einen Hash von Eigenschaften ausgeben würde. Der Konstruktor könnte dann diese Funktion ausführen und sie der Instanz hinzufügen, bevor die andere Verarbeitung durch den Konstruktor erfolgt.

change

Hilfreichster Kommentar

Beim Durchlesen finde ich https://github.com/epicmiller/es2015-default-class-properties einen guten Ansatz. Beim Versuch stellte ich fest, dass Backbone eine integrierte Unterstützung dafür hat. Zum Beispiel:

class MyModel extends Backbone.Model.extend({
   idAttribute: 'id'
}) {
   // ...
};

Der obige Code setzt das MyModel.prototype.idAttribute richtig. Beachten Sie, dass für TypeScript die Deklarationsdatei leicht angepasst werden muss, um eine Konstruktorfunktionsschnittstelle zurückzugeben, aber das ist ein Detail, das für ES6-Benutzer irrelevant ist ...

Alle 63 Kommentare

Ja, das ist definitiv schade. Danke für die Beinarbeit.

Ich denke, die Moral der Geschichte ist, keine ES6-Klassen mit Backbone zu verwenden, zumindest bis die statische Eigenschaft unterstützt wird. Von den von Ihnen vorgeschlagenen Fallback-Optionen ist es meine bevorzugte Lösung, die Strings / Objekte als Rückgabewerte zu definieren. Ein wichtiger Teil des API-Designs von Backbone liegt in diesen von Prototypen gemeinsam genutzten Strings und Objekten, und es würde die API verschmutzen, wenn die Entwickler jede Eigenschaft der Instanz im Konstruktor zuweisen müssten (ganz zu schweigen davon, dass sie Speicher verschwendet).

Gibt es, abgesehen von der Konsistenz, einen Grund, das Schlüsselwort class mit Backbone über extend ?

Toller Blogbeitrag. Ich hatte mich gefragt, wie ES6- und Backbone-Klassen zusammenspielen würden. Zu Ihren Lösungen:

  1. Hängen Sie alle Eigenschaften als Funktionen an : Ich bin nicht sehr dagegen. Es ist nicht so sauber, wie das Objekt direkt auf dem Prototyp zu setzen, aber ich habe eine Menge Code bei mutierenden Prototypobjekten gesehen. Dieser Weg ist immun, weshalb ich denke, ES6 hat sich entschieden, keine Klasseneigenschaften einzubeziehen.
  2. Alle Eigenschaften als Standardoptionen übergeben : Würden Sie etwas in einer klassischeren Sprache nicht so machen? Ich denke, dies ist eine noch weniger saubere Lösung als die oben genannte.
  3. Führen Sie den Konstruktor zweimal aus : Ick.

Ich denke, die Moral der Geschichte ist, keine ES6-Klassen mit Backbone zu verwenden, zumindest bis die statische Eigenschaft unterstützt wird.

Sogar Klasseneigenschaften kommen nach dem super() Aufruf. :enttäuscht:

Gibt es, abgesehen von der Konsistenz, einen Grund, das Schlüsselwort class mit Backbone over Extend zu verwenden?

Dies habe ich im Blogbeitrag angesprochen. Praktisch? Nein. Theoretisch würde es Backbone langfristig ermöglichen, Code und zusätzliche Konzepte zu reduzieren, aber realistischerweise wird es mindestens ein paar Jahre dauern, bis ES6-Klassen auf allen relevanten Browsern ohne Transpiling breit unterstützt werden und die Codereduzierung als nächstes ansteht nichts.

Aber unterschätzen Sie nicht den Aspekt der Konsistenz. Wenn dies "der Weg" für die objektorientierte Programmierung in JavaScript wird (scheint wahrscheinlich angesichts der Standardisierung von Ember/Angular/React/Typescript/Aurelia usw.) andere Optionen. Speziell für Junior-Entwickler. Ich bin mir nicht sicher, ob das unbedingt eine Änderung verdient. Aber es ist nicht nur für die pedantische "Hobgoblin der kleinen Geister" Konsequenz.

Ich stimme @akre54 und @jridgewell zu, dass der Ansatz "Alle Eigenschaften als Funktionen anhängen" wahrscheinlich die beste der vorgeschlagenen Optionen ist. FWIW, ich erinnere mich, dass ich, als ich ursprünglich als relativer js-Neuling Backbone lernte, von diesen "statischen" Eigenschaften und ihrer Verwendung etwas verwirrt war.

ES7 wird korrekte Klasseneigenschaften haben, denke ich https://gist.github.com/jeffmo/054df782c05639da2adb

Der ES7-Vorschlag ist genau das, ein sehr früher von der Community betriebener Vorschlag. Es ist überhaupt nicht klar, dass es jemals Teil einer offiziellen Spezifikation sein wird. Aktuelle Implementierungen führen dazu, dass der Instanz Eigenschaften hinzugefügt werden, NACHDEM der Konstruktor ausgeführt wird, sodass dies bei Backbone nicht hilft. (siehe Link von jridgewell oben oder versuchen Sie es selbst mit Babel 5.0.0)

@jridgewell Ich bezog mich auf diesen Teil von @benmccormicks Beitrag:

React-Entwickler haben die gleichen Probleme mit Eigenschaftsinitialisierern festgestellt, auf die Backbone-Benutzer stoßen. Als Teil der Version 0.13 von React unterstützen sie eine spezielle Eigenschafts-Initialisierungssyntax für Klassen, die eventuell standardisiert werden kann. Weitere Informationen dazu finden Sie in diesem ESDiscuss-Thread . Dieser Standard wird noch ausgearbeitet, aber eine experimentelle Support-Version ist in Babel 5.0.0 verfügbar. Leider definiert diese Version Klasseneigenschaften als instanziiert, nachdem der Superklassenkonstruktor ausgeführt wurde, so dass dies die Probleme von Backbone hier nicht löst.

Siehe zum Beispiel js-decorators Strohmann von Wycats oder den ursprünglichen (abgelösten) Vorschlag für Harmonieklassen .

Ich könnte vorschlagen, dass wir Getter mit Klasseneigenschaften verwenden:

class Row extends Backbone.View {
  get tagName() { return 'li'; }
}

Als absoluten letzten Ausweg könnten wir zum Beispiel statische Requisiten mit einem Helfer à la _.result überprüfen:

_.instOrStaticVar = function(instance, property) {
  if (instance == null) return void 0;
  var value = instance[property] || instance.constructor[property];
  return _.isFunction(value) ? value.call(instance) : value;
}

Ja, aber:

Leider definiert diese Version Klasseneigenschaften als instanziiert, nachdem der Superklassenkonstruktor ausgeführt wurde, so dass dies die Probleme von Backbone hier nicht löst.

Also, ES5'd:

// ES6
class View extends Backbone.View {
  tagName = 'li';

  constructor() {
    // Do anything that doesn't touch `this`
    super();
    // Do anything that touches `this`
  }
}

// ES5
function View() {
  // Do anything that doesn't touch `this`
  Backbone.View.apply(this, arguments);

  // Add class properties
  this.tagName = 'li';

  // Do anything that touches `this`
}
View.prototype = _.create(Backbone.View.prototype, {
  constructor: View
});

Unser Element würde immer noch konstruiert werden, bevor wir eine Änderung zum Setzen der Instanzvariablen erhalten.

Siehe zum Beispiel js-decorators Strohmann von Wycats...

Können Sie erklären, wie sich die Dekorateure bewerben würden?

Ich könnte vorschlagen, dass wir Getter mit Klasseneigenschaften verwenden:

:+1:. Ich sehe das als dasselbe Boot wie alle Eigenschaften als Funktionen anhängen . Nicht so sauber wie das, was wir derzeit haben, aber absolut akzeptabel und mutationssicher.

Als absoluten letzten Ausweg könnten wir zum Beispiel statische Requisiten mit einem Helfer à la _ überprüfen. Ergebnis:

Das könnte interessant sein...

Du könntest es tun:

class MyView extends Backbone.View {
  constructor() {
    super({ tagName: 'h1' });
    this.el.textContent = 'Hello World';
  }
}

@thejameskyle Das ist die aller Eigenschaften als Standardoptionen an die Konstruktoroption der Superklasse . :stuck_out_tongue:

Anstatt sich auf super() zu verlassen, um die Klasse einzurichten, könnten Sie einfach eine init() Funktion oder so etwas haben.

class DocumentRow extends Backbone.View {

    constructor() {
        super();
        this.tagName =  "li";
        this.className = "document-row";
        this.events = {
            "click .icon":          "open",
            "click .button.edit":   "openEditDialog",
            "click .button.delete": "destroy"
        };
        this.init();
    }

    initialize() {
        this.listenTo(this.model, "change", this.render);
    }

    render() {
        //...
    }
}

@milesj hmm? Das wird sofort mit der endgültigen ES6-Klassenspezifikation fehlschlagen

In einer abgeleiteten Klasse müssen Sie super() aufrufen, bevor Sie dies verwenden können

Selbst wenn es funktioniert hat, rufen Sie den Backbone-Konstruktor nie wirklich auf und erhalten seinen Initialisierungscode nicht.

Siehe diesen Link aus meinem ersten Beitrag: http://www.2ality.com/2015/02/es6-classes-final.html

@milesj : Die Sache ist die, Sie müssen super() aufrufen, bevor Sie this.tagName oder ähnliches einstellen. Und da wir ein Element im Konstruktor der Ansicht this.tagName festlegen.

@milesj das ist immer noch nicht erlaubt, wenn Sie Unterklassen bilden.

@jridgewell Oh Entschuldigung, das habe ich übersehen. Es scheint die natürlichste Option zu sein. Ich habe mit Jeffmo und sebmck darüber gesprochen.

Um euch ein paar Hintergrundgeschichten zu geben, der Grund ist, dass this nicht bestimmt wird, bis Sie die Methode super() aufrufen, um die Erweiterung nativer Typen (zB Array) zu unterstützen. Andernfalls stoßen Sie im DOM (und vermutlich an anderen Stellen) auf ein Initialisierungsproblem.

@jridgewell @thejameskyle Dann einfach zuerst super() aufrufen (aktualisiertes Beispiel). Ich sehe das Problem hier wirklich nicht, da ich das gleiche in meinen ES6-Klassen gemacht habe. Verschieben Sie einfach die Konstruktorlogik der Ansichten in die Methode init() .

Das ist eine Menge sehr teurer Code, der zweimal ausgeführt werden muss.

@milesj hast du den ursprünglichen Blogbeitrag gelesen? Die Ausführung von Super First bedeutet, dass die Eigenschaften nicht verarbeitet werden. Eine ausführliche Erklärung finden Sie hier: http://benmccormick.org/2015/04/07/es6-classes-and-backbone-js/

Ja, ich habe es gelesen und bin immer noch neugierig, warum das keine Lösung ist. Alle reden ständig davon, dass der View-Konstruktor aufgerufen werden muss, aber das ist nicht unbedingt der Fall. Warum ist so etwas wie das Folgende keine Lösung (wenn auch etwas erfunden)?

var View = Backbone.View = function(options) {
    this.cid = _.uniqueId('view');
    // extend()ing options is no longer needed if properties are set directly
};

View.prototype.setup = function() {
    this._ensureElement();
    this.initialize.call(this, arguments);
};

class DocumentRow extends Backbone.View {
    constructor() {
        super();
        this.tagName =  "li";
        this.className = "document-row";
        this.events = {
            "click .icon":          "open",
            "click .button.edit":   "openEditDialog",
            "click .button.delete": "destroy"
        };
        this.setup(...arguments);
    }
}

Ich vermute wegen der Abwärtskompatibilität mit Nicht-ES6?

Dann würde die Standardklasse View nicht funktionieren, da der Konstruktor nie #setup aufruft. Und einen Unterklassenaufruf zu etwas anderem als super() erzwingen, wird sehr nervig sein.

Das ist ein Problem, mit dem sich alle ES6-Klassen auseinandersetzen müssen, nicht nur Backbone. Ich persönlich habe es gelöst, indem ich die Eigenschaftenspezifikation der Babel ES7-Klasse verwendet habe.

@milesj Wie bereits erwähnt, lösen ES7-Klasseneigenschaften dieses Problem nicht, da sie erst am Ende des Konstruktors instanziiert werden.

Ich habe mit Jeffmo und sebmck darüber gesprochen:

class Root {
  rootProp = 'root';
  constructor() {
    console.log('Root', this.rootProp);
    console.log('Root', this.derivedProp);
  }
}

class Derived extends Root {
  derivedProp = 'derived';
  constructor() {
    super();
    console.log('Derived', this.rootProp);
    console.log('Derived', this.derivedProp);
  }
}

Entzuckerung zu:

function Root() {
  this.rootProp = 'root';
  console.log('Root', this.rootProp);
  console.log('Root', this.derivedProp);
}

function Derived() {
  super();
  this.derivedProp = 'derived';
  console.log('Derived', this.rootProp);
  console.log('Derived', this.derivedProp);
}

Aber das behebt das Problem hier immer noch nicht und führt zu Inkonsistenzen:

new Derived();
// >> 'Root' 'root'
// >> 'Root' undefined
// >> 'Derived' 'root'
// >> 'Derived' 'derived'

Das ist ein Problem, mit dem sich alle ES6-Klassen auseinandersetzen müssen, nicht nur Backbone.

Hm?

Ich persönlich habe es gelöst, indem ich die Eigenschaftenspezifikation der Babel ES7-Klasse verwendet habe.

Sie werden viele DIV-Elemente ohne className s haben. Siehe den letzten Punkt von https://github.com/jashkenas/backbone/issues/3560#issuecomment -90739676, https://github.com/jashkenas/backbone/issues/3560#issuecomment -91601515 und https://github .com/jashkenas/backbone/issues/3560#issuecomment -98827719.

Ich verstehe. In diesem Fall würde ich vorschlagen, die Option "Alle Eigenschaften als Standardoptionen an den Oberklassenkonstruktor übergeben" oder die letzte Zeile zum Erstellen einer "Eigenschaften" -Methode (die den Konstruktor nicht berührt) zu verwenden.

class DocumentRow extends Backbone.View {
    loadProperties() {
        return {
            tagName: 'li',
            className: 'document-row',
            events: {
                "click .icon": "open",
                "click .button.edit": "openEditDialog",
                "click .button.delete": "destroy"
            },
            foo: 'bar'
        };
    }
}

// Contrived example
var View = Backbone.View = function(options) {
    this.cid = _.uniqueId('view');
    options || (options = {});
    _.extend(this, this.loadProperties(), _.pick(options, viewOptions));
    this._ensureElement();
    this.initialize.apply(this, arguments);
};

Ich habe etwas Ähnliches in Toolkit gemacht, das hier zu sehen ist: https://github.com/titon/toolkit/issues/107

Hi.

Wenn ich die Diskussion hier richtig verstehe - diskutieren die Backbone-Entwickler Workarounds und Best Practices, haben aber nicht die Absicht, tatsächlich Änderungen am BB-Kern vorzunehmen, um dieses Problem zu lösen? (Ich schlage nicht vor, dass sie es sollten, noch hätte ich eine Ahnung, was diese Änderungen sein könnten). Mit anderen Worten, ist der Vorschlag, entweder alle Eigenschaften als Funktionen oder Getter zu verwenden, das letzte Wort zu diesem Thema? Vielen Dank.

@gotofritz Wir diskutieren

Es gibt einige Diskussionen über das Hinzufügen statischer Prototypeigenschaften zu ES7-Klassen, aber bisher nichts Konkretes. In der Zwischenzeit würde ich sagen, bleib bei Backbones extend .

Vielen Dank. Ich probiere ES6-Klassen noch etwas länger aus... :-)

Für alle anderen, die darüber stolpern, finde ich in der Praxis "Alle Eigenschaften als Standardoptionen an den Superklassenkonstruktor übergeben" besser - zum Beispiel hat unsere App dynamische (lokalisierte) Routen, die zum Zeitpunkt der Instanziierung übergeben werden müssen. und eine Route()-Methode zu haben, funktioniert einfach nicht. Während Folgendes gilt:

class Router extends Backbone.Router {

 constructor (localizedRoutes) {
    _.defaults(localizedRoutes, {
        "nonLocalizedRouteA/": "routeA"
        "*actions": "defaultRoute"
     });
 super({ routes: localizedRoutes });
}

Ich habe mir das gerade angesehen und denke, dass beide Problemumgehungen für die Eigenschaft idAttribute eines Modells nicht funktionieren. Eine Methode funktioniert nicht, da Backbone model.idAttribute um auf die Eigenschaft zuzugreifen; Und der Modellkonstruktor scheint das Hinzufügen von Eigenschaften als Optionen insgesamt nicht zu unterstützen.

Ich denke, dass beide Problemumgehungen für die idAttribute-Eigenschaft nicht funktionieren

Ausgezeichneter Fang, ich werde an einer PR arbeiten, die sich damit befasst. In der Zwischenzeit können Sie die Getter-Notation verwenden, um benutzerdefinierte idAttribute (und cidPrefix )

class Model extends Backbone.Model {
  get idAttribute() {
    return '_id';
  }

  get cidPrefix() {
    return '__c';
  }
}

Eine Methode funktioniert nicht, da Backbone model.idAttribute verwendet, um auf die Eigenschaft zuzugreifen

get idAttribute() { return '_id'; } ist eine Getter-Methode, auf die wie auf eine normale Eigenschaft zugegriffen wird. this.idAttribute === '_id'; .

Das hört sich an, als ob eine umfassende Neufassung erforderlich wäre. Backbone v2 vielleicht?

Das hört sich an, als ob eine umfassende Neufassung erforderlich wäre. Backbone v2 vielleicht?

Überhaupt nicht, wir unterstützen bereits die ES6-Unterklasse (mit Ausnahme von Models ). Ich denke, es wäre interessant, wenn jemand den statischen Eigenschaftsvorschlag von @akre54 untersucht, aber selbst das ist bei den beiden Lösungen im ursprünglichen Beitrag nicht erforderlich.

@jridgewell , vielen Dank für die schnelle Lösung!

Dekorateure wurden oben in diesem Thread erwähnt (insbesondere der Vorschlag von Yehuda Katz ), und es war ungelöst, ob dies dieses Problem lösen würde.

Ich habe nur wie vorgeschlagen mit ihnen herumgespielt, und Sie können einen Dekorateur wie folgt schreiben:

function props(value) {
    return function decorator(target) {
        _.extend(target.prototype, value);
    }
}

und dann kann das Beispiel, das wir verwendet haben, so geschrieben werden

@props({
      tagName: 'li',
      className: 'document-row',
      events: {
        "click .icon": "open",
        "click .button.edit": "openEditDialog",
        "click .button.delete": "destroy"
      }
    })
class DocumentRow extends Backbone.View {

    initialize() {
        this.listenTo(this.model, "change", this.render);
    }

    render() {
        //...
    }
}

Das scheint bei mir ganz gut zu funktionieren. Der Dekorator wird auf die Klasse angewendet, bevor der Klassenkonstruktor ausgeführt wird. Dies ist nur eine deklarative Version des Spruchs

class DocumentRow extends Backbone.View {

    initialize() {
        this.listenTo(this.model, "change", this.render);
    }

    render() {
        //...
    }
}
_.extend(DocumentRow.prototype, {
      tagName: 'li',
      className: 'document-row',
      events: {
        "click .icon": "open",
        "click .button.edit": "openEditDialog",
        "click .button.delete": "destroy"
      }
})

Eigentlich habe ich es nicht getestet, aber Sie könnten wahrscheinlich die gesamte Backbone-Erweiterungsfunktion zum Dekorator machen, wenn Sie sowohl statische als auch prototypische Requisiten haben möchten.

Leider ist dies vorerst nur ein Vorschlag, aber Babel unterstützt ihn hinter einer experimentellen Flagge. Wenn die Leute also abenteuerlustig sind, ist dies hier eine mögliche Lösung.

@benmccormick , die Decorator-Technik funktioniert gut für mich. Gibt es, abgesehen davon, dass dies nur ein Vorschlag ist, noch andere Bedenken, diesen Ansatz zu verfolgen?

@andrewrota Ich schreibe gerade buchstäblich einen Blogbeitrag, der diesen durchgelesen, als du kommentiert hast). Das ist ein großes "anders als", aber ich persönlich sehe keine. Ich denke jedoch, dass wir es besser machen können als das, was ich oben beschrieben habe, und einige nette neue Schnittstellen für Backbone mit Dekoratoren erstellen.

Siehe diesen Kern von @StevenLangbroek , der mich ursprünglich dazu gebracht hat, darüber nachzudenken: https://gist.github.com/StevenLangbroek/6bd28d8201839434b843

Hier ist eine Vorschau auf den Folgebeitrag, den ich veröffentliche: http://benmccormick.org/2015/07/06/backbone-and-es6-classes-revisited/ Jetzt mit permanentem Link aktualisiert

Es wird Anfang dieser Woche irgendwann auf eine permanente URL umziehen. Aber die grundlegende Zusammenfassung aus diesem Thread und was ich gelernt habe, ist:

Es gibt 3 Ansätze, um Backbone-Eigenschaften mit der aktuellen ES6-Klassenspezifikation zu verwenden (die ersten beiden benötigen #3684, um als vollständig unterstützt zu gelten):

  1. Übergeben Sie alle Eigenschaften an den Super im Konstruktor
  2. Behandeln Sie alle Eigenschaften als Methoden
  3. Fügen Sie Eigenschaften direkt zum Prototyp hinzu, nachdem eine Klasse deklariert wurde

All dies empfinde ich immer noch als Einschränkung der Ausdruckskraft in gewissem Maße. Aber ich denke, das Problem wird sich mehr oder weniger lösen, wenn Dekorateure eine offizielle Spezifikation werden. Bei Dekorateuren gibt es 2 weitere Optionen.

  1. Fügen Sie einen Requisiten-Dekorateur hinzu, der die Requisiten an der Spitze der Klasse aufnimmt und sie dem Prototyp hinzufügt
  2. Erstellen Sie mehrere Spezialdekoratoren, die eine ausdrucksvollere/feinkörnigere Benutzeroberfläche ermöglichen.

Ich glaube nicht, dass irgendeine dieser Lösungen außer #3684 zusätzliche Modifikationen an Backbone erfordert, aber es wäre eine interessante Rolle für eine Backbone-Decorator-Bibliothek, wenn/wenn Decorators standardisiert werden.

Würde mich über Feedback zu dem Beitrag freuen, bevor ich ihn am Montag/Dienstag veröffentliche.

@benmccormick Ich dachte, Dekorateure werden bewertet, bevor eine Konstruktion stattfindet, danke für die Korrektur. Ich werde das Wesentliche in Kürze aktualisieren. außerdem: Tausend Dank für die Erwähnung im Blogpost :) ping mich auf Twitter, wenn du es veröffentlichst? :+1:

Wir könnten dieselbe Syntax für modelEvents und collectionEvents in Marionette verwenden, nur nicht für Trigger. Diese könnten durch einen Klassen-Dekorator (wie tagName und Template in Ihrem Blog-Post) verfügbar gemacht werden, aber ich dachte: können wir dafür nicht statische Eigenschaften verwenden? Oder geht das im Backbone nicht?

Ich verstehe, dass Dekorateure immer noch Stufe 0 sind, aber ich denke, sie werden ein großartiges Upgrade für die Art und Weise sein, wie wir Backbone-Apps schreiben, insbesondere die Methode Dekoratoren über einen Ereignis-Hash.

@StevenLangbroek siehe oben für Diskussionen über statische Eigenschaften.

Die derzeit angegebene Syntax erstellt eine lokale Eigenschaft für jede Instanz, anstatt sie zum Prototyp hinzuzufügen. Diese Eigenschaften werden hinzugefügt, nachdem der Superkonstruktor ausgeführt wurde.

@benmccormick , der Beitrag sieht gut aus und ich denke, er erklärt die Kompromisse mit jeder der Optionen gut. An dieser Stelle mag ich den Ansatz der Spezialdekoratoren sehr, und es scheint der beste Ansatz zu sein, vorausgesetzt, dass Dekorateure es in die Spezifikation schaffen.

Sollte @benmccormick 's Dekorateur ruft _#extend mit dem Konstruktor nicht der Prototyp und dann @ akre54' s _.instOrStaticVar Methode wird anstelle von _#result ? Mir ist klar, dass das eine bahnbrechende Änderung wäre, aber auf diese Weise scheint es IMHO sauberer zu sein. Wie @akre54 darauf hingewiesen hat, sind so definierte Eigenschaften von Prototypen gemeinsam genutzte Zeichenfolgen und Objekte (dh von allen Instanzen gemeinsam genutzt), daher sollte auf sie über die Klasse zugegriffen werden, oder?

Ich gehe weiter und mache die Eigenschaften der Arbeiterklasse so, wie wir es brauchen. Klasseneigenschaften können auch mit Anmerkungen versehen werden, und wir können einen speziellen Dekorator erstellen, der dekorierte Eigenschaften mit Prototypen verknüpft.

class TodoView extends Backbone.View {
  <strong i="6">@protoprop</strong>
  static tagName = 'li';
}

function protoprop(target, name, descriptor) {
  target.prototype[name] = descriptor.initializer()
}

Siehe Babel REPL mit Beispiel. Es basiert auf experimentellen Dingen, aber es funktioniert.

@just-boris Wie in meinen Blogkommentaren besprochen, ist das Verhalten, das Sie dort sehen, ein Implementierungsdetail von Babels Umgang mit den Klasseneigenschaften und Dekoratorspezifikationen. Sein Verhalten ist derzeit in keinem Angebot definiert. Wenn Sie dies auf diese Weise tun möchten, sollten Sie hier und/oder hier Probleme machen, um Dekoratoren für Klasseneigenschaften zu einem standardisierten Verhalten zu machen. Andernfalls könnte (und wird wahrscheinlich) das, was Sie tun, jederzeit kaputt gehen.

@benmccormick wycats/javascript-decorators hat bereits eine zusätzliche Definition in Bezug auf Eigenschaftsinitialisierer .

Das Hauptanliegen dort, dass Eigenschaftsinitialisierer ein üblicher Deskriptor sind, sowie Klassenmethoden, so können Dekoratoren sie auch einschließen. Ich sehe keinen Grund zur Besorgnis, während die Spezifikationen in diesem Abschnitt unverändert bleiben

Ah sehr cool, das hatte ich noch nicht gesehen. Danke für den Hinweis.

Am Mo, 21. September 2015 um 11:29 Uhr, Boris Serdiuk [email protected]
schrieb:

@benmccormick https://github.com/benmccormick
https://github.com/wycats/javascript-decorators hat bereits extra
Definition zu Eigenschaftsinitialisierern
https://github.com/wycats/javascript-decorators/blob/master/INITIALIZER_INTEROP.md
.

Das Hauptanliegen dort, dass Eigenschaftsinitialisierer ein üblicher Deskriptor sind,
sowie Klassenmethoden, sodass Dekorateure sie ebenfalls einhüllen können. ich sehe nicht
Grund zur Sorge, während die Spezifikationen in diesem Abschnitt unverändert bleiben


Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/jashkenas/backbone/issues/3560#issuecomment -142015454
.

Wollte nur die Vor- und Nachteile der Verwendung von https://github.com/typhonjs/backbone-es6 gegenüber der von @benmccormick vorgeschlagenen Methodentechnik wissen.

Übrigens, danke @benmccormick für den

Zusätzlich zum Vorschlag (#121) Pull-Request, der hier properties Methode in Aktion anhängt https://github.com/dsheiko/backbone-abstract/tree/master/demo-es6/src/Js
Wie @akre54 erwähnt hat Justin bereits eine ähnliche Lösung vorgeschlagen ( preInitialize Methode). Während ich es bereits in meiner Filiale verwende, löst es das Problem für mich wirklich. Scheint auch in TypeScript nützlich zu sein, obwohl sie keine deklarativen Klasseneigenschaften verbieten.

PS preInitialize klingt in diesem Zusammenhang allgemeiner und daher besser. Allerdings ist es eher wie preConstruct wenn wir die Methode vor allen Jobs des Konstruktors aufrufen

Ich wünschte wirklich, wir würden einen neuen Vorschlag für Klasseneigenschaften sehen, der sie auf dem Prototyp festlegt. Es scheint, dass viele, die an dem Vorschlag beteiligt sind, besorgt über die Auswirkungen sind, aber ich finde es unglaublich inkonsistent, dass Klassenmethoden direkt an den Prototyp angehängt werden, während Jeffmos Vorschlag sie in den Konstruktor steckt.

Hätten sie Eigenschaften direkt an den Prototyp angehängt, könnten Sie so ziemlich jeden React/Backbone-Code in ES2015-Klassen migrieren.

toller Blogpost @benmccormick !! werde diese Dekorateure in meinem Projekt verwenden

@benmccormick , ich habe eine andere Möglichkeit gefunden, Klassen mit Standardeigenschaften zu deklarieren, schau mal: https://github.com/epicmiller/es2015-default-class-properties

Es läuft normalerweise in jeder Umgebung, die Klassen nativ unterstützt, gut transpiliert und viel schöner aussieht, als sie im Konstruktor oder nach der Deklaration zu definieren. Mit Vorschlägen für Dekoratoren und Klasseneigenschaften, die für ES2016/ES2017 in der Pipeline sind, mag dies eher eine akademische Übung sein als die langfristige Lösung für Backbone, aber so etwas ist definitiv eine praktikable Option, wenn 2-3 Jahre zu lang sind Warten.

Nun, die Sache ist die, dass sich Klasseneigenschaften immer noch auf Stufe 1 im Ecmascript-Vorschlagsstufensystem befinden. Ich habe keine Ahnung warum, da es wie eine Spielerei in Bezug auf "was der Benutzer bekommt" aussieht. Natürlich habe ich keine Ahnung, was für Dinge sowohl syntaktisch als auch in Bezug auf Referenzimplementierungen unter der Haube brechen könnten.

https://github.com/tc39/ecma262
https://github.com/jeffmo/es-class-fields-and-static-properties

Beim Durchlesen finde ich https://github.com/epicmiller/es2015-default-class-properties einen guten Ansatz. Beim Versuch stellte ich fest, dass Backbone eine integrierte Unterstützung dafür hat. Zum Beispiel:

class MyModel extends Backbone.Model.extend({
   idAttribute: 'id'
}) {
   // ...
};

Der obige Code setzt das MyModel.prototype.idAttribute richtig. Beachten Sie, dass für TypeScript die Deklarationsdatei leicht angepasst werden muss, um eine Konstruktorfunktionsschnittstelle zurückzugeben, aber das ist ein Detail, das für ES6-Benutzer irrelevant ist ...

@t-beckmann das ist eine ganz nette Lösung - sieht lesbar aus und erfordert nur minimale Änderungen. Vielen Dank!

Mir ist klar, dass dieser Thread jetzt 2 Jahre dauert, aber er ist immer noch eines der besten (und einzigen) Ergebnisse bei der Suche nach Backbone- und ES6-Klassen, und ich dachte, ich würde eine potenzielle Lösung vorstellen, die hier mehrmals erwähnte Klasseneigenschaften verwendet .

Da die Klasseneigenschaften jetzt in Stufe 2 und mit dem Babel-Preset allgemein verfügbar sind, dachte ich, ich schaue mir das mal anders an. Wie bereits erwähnt, besteht das Problem bei Instanz-/Mitgliedseigenschaften darin, dass sie erst _nach_ constructor() auf den Prototyp angewendet werden, aber viele der Eigenschaften, die festgelegt werden müssen, werden innerhalb des Konstruktors verwendet. Statische Eigenschaften werden sofort angewendet, aber (absichtlich) nicht in Instanzen der Klasse kopiert.

Das folgende Shim kopiert statische Eigenschaften vom Konstruktor auf die Instanz, bevor der Konstruktor ausgeführt wird (effektiv einen neuen Konstruktor erstellen, die Eigenschaften anwenden und dann den ursprünglichen Konstruktor ausführen). Obwohl es definitiv ein Hack ist, bin ich mit dem Ergebnis ziemlich zufrieden:

Die Beilage:

export default function StaticShim(Ctor) {
    const NewCtor = function shim(...args) {
       Object.keys(Ctor).forEach((key) => {
            if (this[key] === undefined) {
                this[key] = toApply[key];
            }
        });

        Object.assign(this, this.constructor);

        Ctor.apply(this, args);
    };

    NewCtor.prototype = Object.create(Ctor.prototype);
    NewCtor.prototype.constructor = NewCtor;

    Object.keys(Ctor).forEach((key) => {
        if (NewCtor[key] === undefined) {
            NewCtor[key] = Ctor[key];
        }
    });

    return NewCtor;
}

Und dann im Gebrauch:

class TestModel extends StaticShim(Backbone.Model) {
    static idAttribute = '_id';
    static urlRoot = '/posts';

    initialize() {
        console.log(this.url()); // Correctly logs "/posts/{id}"
    }
}

Wollte es nur hier ablegen, falls es anderen hilft oder jemand eine Meinung dazu hat. Vielen Dank!

Obligatorische Entschuldigung für die Wiederbelebung eines alten Problems.

Wäre es möglich oder wert, ein Babel-Plugin zu schreiben, das eine ES6-Klassendeklaration so umwandelt, dass sie Backbone.*.extend({...}) verwendet?

@enzious scheint definitiv möglich zu sein. Ob es sich lohnt liegt an dir :)

Die Lösung von @t-beckmann scheint am einfachsten zu sein. Sollten wir das in das Backbone selbst integrieren?

Für mich sieht es nicht richtig aus, wäre es nicht besser, eine Methode zu haben, die das idAttribute setzt?

Außerdem wäre es toll, wenn es Promise-Support gäbe. Dies ist ein nativerer Ansatz als die Verwendung von jquery Deferred, die ich persönlich gerne in Backbone als veraltet sehen würde.

Die Geschichte hier ist noch sehr unklar für die Aktualisierung älterer Backbone-Anwendungen, um moderne Tools und Sprachfunktionen zu nutzen. Es ist besonders enttäuschend zu sehen, dass Dinge wie Symbol.iterator implementiert und nicht in einer Produktionsversion verfügbar sind.

Für diejenigen, die immer noch nach klareren Antworten auf diese Frage suchen, füge ich TypeScript zu einer Backbone-App hinzu und fand die Lösung aus diesem Kommentar am hilfreichsten.

Bisher funktioniert es gut, mit dem Nachteil, Eigenschaften, die durch den Dekorator übergeben werden, explizit annotieren zu müssen, anstatt eine schönere Inferenz zu haben.

export function Props<T extends Function>(props: { [x:string]: any }) {
  return function decorator(ctor: T) {
    Object.assign(ctor.prototype, props);
  };
}

@Props({
  routes: {
    home: "home",
    about: "about",
    dashboard: "dashboard",
    blog: "blog",
    products: "products",
    accountSettings: "accountSettings",
    signOut: "signOut",
  },
})
export class Router extends Backbone.Router {
  home() {}
  about() {}
  // ...
}

@Props({
  model: CategoryModel,
  comparator: (item: CategoryModel) => item.display_value,
})
export class CategoryCollection extends Backbone.Collection<CategoryModel> {}

Beispiel für eine explizite Eigenschaftsanmerkung:

image

@raffomania , @jridgewell & Co., mein Team hat dieses Problem umgangen, indem es dem Prototyp außerhalb der Klasse idAttribute hinzugefügt hat.

class Beispiel erweitert ParentExample {
// Klassenmethoden usw. hier
}

x.Beispiel = Beispiel;

x.Example.prototype.idAttribute = 'customIdAttr';

@kamsci Ich habe das gleiche in diesem Zweig gemacht, wo ich Backbone in ES6-Klassen umgewandelt habe

Backbone verwendet _configuration_ bis zu dem Punkt, dass die Konfigurationsobjekte _declarative_ sind. Das ist nett, aber es wird nie gut mit Vererbung spielen. (Klonen Sie die Klasse und konfigurieren Sie sie. Das ist keine Vererbung.)

Wenn wir neuen Code mit Backbone schreiben, ist es in Ordnung, anders zu denken. Das Ausschneiden und Einfügen von ES5-Code und dann das Aussehen von ES6 funktioniert nicht. Na und?

Ich habe überhaupt kein Problem, alles über ein Konfigurationsobjekt einzugeben. Wie wir den Inhalt dieser Konfiguration veröffentlichen oder das Lesen/Arbeiten mit ihr erleichtern, ist ein Problem, das es zu lösen gilt, und nicht darum, darüber zu weinen.

Niemand möchte einen Konstruktor zweimal ausführen. Das ist dumm. Aber das Muster von

Foo = BackboneThing.extend({LONG DECLARATIVE OBJECT LITERAL}) ist auch mutterliebend hässlich. Ihr alle macht es nur so lange, dass ihr nicht seht, wie hässlich es ist.

Zur Info: Ich habe ein großes Marionettenprojekt und wollte die ES6-Syntax verwenden. Ich habe einen jscodeshift-Transformator erstellt, der Backbone-Extended-Deklarationen in ES6-Klassen übersetzt. Es macht viele vereinfachende Annahmen, kann aber für einige von Ihnen dennoch nützlich sein, wenn auch nur als Ausgangspunkt. Es folgt der von @t-beckmann vorgeschlagenen Syntax, als ich auf Probleme mit Dekorateuren stieß.
https://gist.github.com/maparent/83dfd65a37aaaabc4072b30b67d5a05d

Für mich scheint in diesem Thread eine seltsame Fehlbezeichnung zu sein. 'statische Eigenschaften' für ES6 sind Eigenschaften des Konstruktors, die in der Klasse ohne Instanziierung existieren (zB Class.extend). In diesem Thread scheinen sich 'statische Eigenschaften' auf benannte Attribute des Prototyps mit einem 'statischen' Wert zu beziehen (keine Getter oder Funktionen). Habe ich das richtig verstanden?

Für Prototypeigenschaften mit einem statischen Wert ist das Deklarieren der Backbone-Vorinitialisierungswerte als Funktionsrückgabewerte ein recht einfacher Übergang und funktioniert gut, da _.result wie erwartet für Standardwerte, Klassenname, ID usw. funktioniert. Andere Instanzeigenschaften scheinen gut deklariert zu sein bei oben in der Initialisierungsfunktion wie gewohnt. Dieses Problem scheint nur aufzutreten, da Sie in ES6-Klassen derzeit keine Prototypeigenschaften mit einem statischen Wert definieren können, sondern nur Getter, Setter und Funktionen.

In jedem Fall werden statische Eigenschaften des Konstruktors/der Klasse (Class.extend) nicht wie in ES6 im Backbone vererbt. Backbone kopiert die statischen Klasseneigenschaften jedes Mal in die neue Klasse/den neuen Konstruktor, wenn die Erweiterungsfunktion ausgeführt wird, anstatt diese Eigenschaften wie bei ES6 erben zu lassen. Ich habe hier einen PR gemacht, um das zu beheben https://github.com/jashkenas/backbone/pull/4235

Ich würde mich über ein paar Kommentare / Feedback freuen, ich bin mir nicht sicher, ob es etwas kaputt machen wird, ich habe es ziemlich viel getestet und es scheint gut zu funktionieren. Backbone-Klassen erben Class.extend hinterher, anstatt einen Verweis auf jeden neuen Konstruktor zu kopieren.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen