Backbone: Cours Backbone et ES6

Créé le 7 avr. 2015  ·  63Commentaires  ·  Source: jashkenas/backbone

Avec les dernières modifications apportées à la spécification de classe ES6 (détails ici ), il n'est plus possible d'utiliser les classes ES6 avec Backbone sans faire de compromis significatifs en termes de syntaxe. J'ai écrit une description complète de la situation ici (assurez-vous de cliquer sur les commentaires en bas pour une option d'atténuation supplémentaire), mais il n'y a essentiellement aucun moyen d'ajouter des propriétés à une instance d'une sous-classe avant les parents des sous-classes constructeur en cours d'exécution.

Donc ça:

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() {
        //...
    }
}

n'est plus valide dans la spécification finale ES6. Au lieu de cela, vous avez effectivement 3 options (pas très attrayantes) si vous voulez essayer de faire fonctionner cela :

Attacher toutes les propriétés en tant que fonctions

Backbone le permet, mais cela semble stupide d'écrire quelque chose comme ça :

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() {
        //...
    }
}

par rapport à la syntaxe d'extension actuelle

Exécutez le constructeur deux fois

Je ne considère pas cela comme une véritable option en raison des problèmes que cela entraînerait lors de l'exécution de l'initialisation une deuxième fois avec différents CID, etc.

Transmettez toutes les propriétés en tant qu'options par défaut au constructeur de la superclasse

Cela a été suggéré par un commentateur sur mon blog et est probablement l'option actuelle la plus pratique. Cela ressemble à quelque chose comme ça :

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

Étant donné que toutes ces options actuelles impliquent des compromis clairs par rapport à la syntaxe actuelle des extensions Backbone, ce serait merveilleux si une meilleure solution pouvait être développée. Je ne sais pas exactement à quoi cela devrait ressembler, mais une idée qui m'est venue à l'esprit pendant la rédaction de mon blog était l'ajout d'une fonction "propriétés" qui produirait un hachage de propriétés. Le constructeur pourrait ensuite exécuter cette fonction et les ajouter à l'instance avant l'autre traitement effectué par le constructeur.

change

Commentaire le plus utile

En lisant ceci, je trouve que https://github.com/epicmiller/es2015-default-class-properties est une bonne approche. En essayant, j'ai réalisé que Backbone avait un support intégré pour cela. Par exemple:

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

Le code ci-dessus définira correctement MyModel.prototype.idAttribute. Remarquez que pour TypeScript, le fichier de déclaration doit être légèrement ajusté pour renvoyer une interface de fonction de constructeur, mais c'est un détail non pertinent pour les utilisateurs d'ES6...

Tous les 63 commentaires

Ouais, c'est vraiment une déception. Merci d'avoir fait les démarches.

Je suppose que la morale de l'histoire est de ne pas utiliser les classes ES6 avec Backbone, au moins jusqu'à ce que la propriété statique supporte les terres. Parmi les options de secours que vous avez proposées, ma solution préférée consiste à définir les chaînes / objets en tant que valeurs de retour. Un élément clé de la conception de l'API de Backbone réside dans ces chaînes et objets partagés par prototype, et cela salirait l'API pour obliger les développeurs à attribuer chaque propriété à l'instance dans le constructeur (sans parler du gaspillage de mémoire).

Mis à part la cohérence, y a-t-il une raison d'utiliser le mot-clé class avec Backbone sur extend ?

Excellent article de blog. Je me demandais comment les classes ES6 et Backbone joueraient ensemble. Quant à vous des solutions :

  1. Attachez toutes les propriétés en tant que fonctions : je ne suis pas super opposé à cela. Ce n'est pas aussi simple que de définir l'objet directement sur le prototype, mais j'ai vu une tonne de code trébucher sur la mutation d'objets prototypes. Cette façon est immunisée, c'est pourquoi je pense que ES6 a choisi de ne pas inclure de propriétés de classe.
  2. Passer toutes les propriétés comme options par défaut : n'est-ce pas ainsi que vous feriez quelque chose dans un langage plus classique ? J'ai l'impression que c'est une solution encore moins propre que la précédente.
  3. Exécutez le constructeur deux fois : Ick.

Je suppose que la morale de l'histoire est de ne pas utiliser les classes ES6 avec Backbone, au moins jusqu'à ce que la propriété statique supporte les terres.

Même les propriétés de classe viennent après l'appel super() . :déçu:

Mis à part la cohérence, y a-t-il une raison d'utiliser le mot-clé class avec Backbone over extend ?

J'en ai parlé dans l'article du blog. Pratiquement? Non. En théorie, cela permettrait à Backbone à long terme de réduire le code et les concepts supplémentaires, mais en réalité, il faudra au moins quelques années avant que les classes ES6 ne soient largement prises en charge sur tous les navigateurs pertinents sans transpilation, et la réduction de code serait la prochaine à rien.

Mais ne sous-estimez pas l'aspect de cohérence. Si cela devient "la façon" de faire de la programmation orientée objet en JavaScript (cela semble probable étant donné la normalisation à ce sujet de Ember/Angular/React/Typescript/Aurelia, etc.), Backbone ne l'utilisera pas sera une courbe d'apprentissage supplémentaire pour la bibliothèque par rapport à autres options. Surtout pour les développeurs juniors. Je ne suis pas sûr que cela mérite nécessairement un changement. Mais ce n'est pas seulement pour la cohérence pédante de "hobgoblin of small minds".

Je suis d'accord avec @akre54 et @jridgewell que l'

ES7 aura des propriétés de classe correctes, je suppose https://gist.github.com/jeffmo/054df782c05639da2adb

La proposition ES7 n'est que cela, une proposition très précoce axée sur la communauté. Pas du tout clair qu'il fera jamais partie d'une spécification officielle. Les implémentations actuelles entraînent l'ajout de propriétés à l'instance APRÈS l'exécution du constructeur, cela n'aide donc pas avec Backbone. (voir le lien de jridgewell ci-dessus ou essayez-le vous-même avec Babel 5.0.0)

@jridgewell Je post de

Les développeurs de React ont noté les mêmes problèmes avec les initialiseurs de propriétés que les utilisateurs de Backbone rencontrent. Dans le cadre de la version 0.13 de React, ils prennent en charge une syntaxe d'initialisation de propriété spéciale pour les classes, qui peut éventuellement être standardisée. Il y a plus d'informations à ce sujet dans ce fil de discussion ESDiscuss . Cette norme est toujours en cours d'élaboration mais une version expérimentale de support est disponible dans Babel 5.0.0. Malheureusement, cette version définit les propriétés de classe comme étant instanciées après l'exécution du constructeur de la superclasse, donc cela ne résout pas les problèmes de Backbone ici.

Voir par exemple js-decorators Strawman de wycats ou la proposition originale (remplacée)

Je pourrais suggérer que nous utilisions des getters avec des propriétés de classe :

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

En dernier recours absolu, nous pourrions vérifier par exemple ou des accessoires statiques avec un assistant à la _.result :

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

Oui, mais :

Malheureusement, cette version définit les propriétés de classe comme étant instanciées après l'exécution du constructeur de la superclasse, donc cela ne résout pas les problèmes de Backbone ici.

Donc, ES5 :

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

Notre élément serait toujours construit avant que nous ayons une modification pour définir la variable d'instance.

Voir par exemple l'homme de paille js-decorators de wycats...

Pouvez-vous expliquer comment les décorateurs s'appliqueraient?

Je pourrais suggérer que nous utilisions des getters avec des propriétés de classe :

:+1:. Je vois cela comme le même bateau que d' attacher toutes les propriétés en tant que fonctions . Pas aussi propre que ce que nous avons actuellement, mais parfaitement acceptable et à l'épreuve des mutations.

En dernier recours absolu, nous pourrions vérifier par exemple ou des accessoires statiques avec un assistant à la _.result :

ça peut être intéressant...

Vous pourriez faire:

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

@thejameskyle C'est l' toutes les propriétés comme options par défaut à l' option du

Au lieu de compter sur super() pour configurer la classe, vous pourriez simplement avoir une fonction init() ou quelque chose du genre.

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 hum ? Cela entraînera une erreur immédiatement avec la spécification finale de la classe ES6

Dans une classe dérivée, vous devez appeler super() avant de pouvoir utiliser ceci

Même si cela a fonctionné, vous n'appelez jamais le constructeur Backbone et n'obtiendrez pas son code d'initialisation.

Voir ce lien de mon premier post : http://www.2ality.com/2015/02/es6-classes-final.html

@milesj : Le truc, c'est que vous devez appeler super() avant de régler this.tagName ou autre. Et, puisque nous garantissons un élément dans le constructeur de la vue, nous avons déjà créé un élément avant de définir this.tagName .

@milesj ce n'est toujours pas autorisé lorsque vous sous-

@jridgewell Oh désolé, j'ai raté ça. Cela semble être l'option la plus naturelle. J'en ai parlé à Jeffmo et Sebmck.

Pour vous donner un peu d'histoire, le raisonnement est que pour prendre en charge l'extension des types natifs (c'est-à-dire Array), this n'est déterminé que lorsque vous appelez la méthode super() . Sinon, vous rencontrez un problème d'initialisation dans le DOM (et probablement à d'autres endroits).

@jridgewell @thejameskyle Ensuite, appelez simplement super() d'abord (exemple mis à jour). Je ne vois vraiment pas le problème ici car j'ai fait la même chose dans mes cours ES6. Déplacez simplement la logique du constructeur de vues vers la méthode init() .

C'est beaucoup de code très coûteux à exécuter deux fois.

@milesj avez-vous lu l'article de blog original ? Exécuter super first signifie que les propriétés ne sont pas traitées. Voir ici pour une explication détaillée : http://benmccormick.org/2015/04/07/es6-classes-and-backbone-js/

Oui, je l'ai lu, et je suis toujours curieux de savoir pourquoi ce n'est pas une solution. Tout le monde n'arrête pas de parler du constructeur de vues qui doit être appelé, mais ce n'est pas nécessairement le cas. Pourquoi quelque chose comme ce qui suit n'est-il pas une solution (bien qu'un peu artificiel) ?

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

Je suppose à cause de la rétrocompatibilité avec les non-ES6?

Ensuite, la classe par défaut View ne fonctionnerait pas puisque le constructeur n'appelle jamais #setup . Et forcer une sous-classe à appeler autre chose que super() va être super ennuyeux.

C'est un problème auquel toutes les classes ES6 doivent faire face, pas seulement Backbone. Je l'ai personnellement résolu en utilisant la spécification des propriétés de la classe Babel ES7.

@milesj Comme indiqué précédemment, les propriétés de classe ES7 ne résolvent pas ce problème car elles ne sont pas instanciées avant la fin du constructeur.

J'ai parlé à jeffmo et sebmck de faire ceci:

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

Désucrage à :

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

Mais cela ne résout toujours pas le problème ici et conduit à une incohérence :

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

C'est un problème auquel toutes les classes ES6 doivent faire face, pas seulement Backbone.

Hum ?

Je l'ai personnellement résolu en utilisant la spécification des propriétés de la classe Babel ES7.

Vous allez avoir beaucoup d'éléments DIV sans className s. Voir le dernier point de https://github.com/jashkenas/backbone/issues/3560#issuecomment -90739676, https://github.com/jashkenas/backbone/issues/3560#issuecomment -91601515 et https://github .com/jashkenas/backbone/issues/3560#issuecomment -98827719.

Je vois. Dans ce cas, je suggérerais d'utiliser l'option "Passer toutes les propriétés comme options par défaut au constructeur de la superclasse", ou la dernière ligne sur la création d'une méthode "propriétés" (qui ne touche pas le constructeur).

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

J'ai fait quelque chose de similaire dans Toolkit, que vous pouvez voir ici : https://github.com/titon/toolkit/issues/107

Salut.

Si je comprends bien la discussion ici - les développeurs de Backbone discutent des solutions de contournement et des meilleures pratiques, mais n'ont-ils aucune intention d'apporter des modifications au noyau BB pour résoudre ce problème ? (Je ne suggère pas qu'ils le devraient, et je n'aurais aucune idée de ce que pourraient être ces changements). En d'autres termes, la suggestion d'utiliser toutes les propriétés comme fonctions ou getters est-elle le dernier mot sur le sujet ? Merci.

@gotofritz Nous discutons de solutions de contournement car la solution d'ES6 consistant à forcer toutes les propriétés à vivre sur des instances ne s'adapte pas. Le système de classe de Backbone fait ce qu'il faut ici.

Il y a des discussions sur l' ajout de propriétés de prototype statiques aux classes ES7 mais jusqu'à présent, rien de concret. En attendant, je dirais de rester avec le extend Backbone.

Merci. Je vais essayer les cours ES6 un peu plus longtemps... :-)

Pour le bénéfice de tous ceux qui trébuchent là-dessus, dans la pratique, je trouve que "Passer toutes les propriétés en tant qu'options par défaut au constructeur de la superclasse" - par exemple, notre application a des routes dynamiques (localisées) qui doivent être transmises au moment de l'instanciation, et avoir une méthode routes() ne fonctionne tout simplement pas. Alors que ce qui suit fait

class Router extends Backbone.Router {

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

Je viens de jeter un coup d'œil à cela et je pense que les deux solutions de contournement ne fonctionnent pas pour la propriété idAttribute un modèle. Une méthode ne fonctionnera pas car Backbone utilise model.idAttribute pour accéder à la propriété ; Et le constructeur de modèle ne semble pas prendre en charge l'ajout de propriétés en tant qu'options.

Je pense que les deux solutions de contournement ne fonctionnent pas pour la propriété idAttribute

Excellente prise, je vais travailler sur un PR abordant cela. En attendant, vous pouvez utiliser la notation getter pour fournir des idAttribute (et cidPrefix ):

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

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

Une méthode ne fonctionnera pas car Backbone utilise model.idAttribute pour accéder à la propriété

get idAttribute() { return '_id'; } est une méthode getter, accessible comme une propriété normale. this.idAttribute === '_id'; .

Cela commence à ressembler à une réécriture majeure est nécessaire. Backbone v2 peut-être ?

Cela commence à ressembler à une réécriture majeure est nécessaire. Backbone v2 peut-être ?

Pas du tout, nous prenons déjà en charge le sous-classement ES6 (à l'exception de Models ). Je pense que ce serait intéressant si quelqu'un explorait la suggestion de propriété statique de @ akre54 , mais même cela n'est pas nécessaire avec les deux solutions du message d'origine.

@jridgewell , merci beaucoup pour la solution rapide !

Les décorateurs ont été mentionnés ci-dessus dans ce fil (en particulier la proposition de Yehuda Katz ), et il n'a pas été résolu si cela résoudrait ce problème.

Je jouais juste avec eux comme proposé, et vous pouvez écrire un décorateur comme ceci :

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

et puis l'exemple que nous avons utilisé peut être écrit comme ceci

@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() {
        //...
    }
}

Cela semble fonctionner très bien pour moi. Le décorateur est appliqué à la classe avant l'exécution du constructeur de classe. Ceci est juste une version déclarative de dire

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

En fait, je ne l'ai pas testé, mais vous pourriez probablement faire fonctionner l'ensemble du backbone en tant que décorateur si vous vouliez à la fois des accessoires statiques et des prototypes.

Malheureusement, ce n'est qu'une proposition pour l'instant, mais Babel la soutient derrière un drapeau expérimental, donc si les gens se sentent aventureux, c'est une solution possible ici.

@benmccormick , la technique du décorateur me

@andrewrota J'écris littéralement un article de blog sur le suivi de ce sujet en ce moment (je lisais ce fil lorsque vous avez commenté). C'est un gros "autre que", mais je n'en vois personnellement aucun. En fait, je pense que nous pouvons faire mieux que ce que j'ai décrit ci-dessus et créer de nouvelles interfaces sympas pour Backbone avec des décorateurs.

Voir cet aperçu de https://gist.github.com/StevenLangbroek/6bd28d8201839434b843

Voici un aperçu du post de suivi que je publie : http://benmccormick.org/2015/07/06/backbone-and-es6-classes-revisited/ Mis à jour avec un lien permanent maintenant

Il passera à une URL permanente au début de cette semaine. Mais le résumé de base de ce fil et ce que j'ai appris est:

Il existe 3 approches pour faire fonctionner les propriétés Backbone avec la spécification actuelle des classes ES6 (les 2 premières ont besoin de #3684 pour être considérées comme entièrement prises en charge) :

  1. Passer toutes les propriétés au super dans le constructeur
  2. Traiter toutes les propriétés comme des méthodes
  3. Ajouter des propriétés directement au prototype après qu'une classe a été déclarée

Je considère toujours que tout cela limite l'expressivité dans une certaine mesure. Mais je pense que le problème sera plus ou moins résolu si les décorateurs deviennent un cahier des charges officiel. Avec les décorateurs, il y a 2 autres options.

  1. Ajouter un décorateur d'accessoires qui prend les accessoires en haut de la classe et les ajoute au prototype
  2. Créez plusieurs décorateurs à usage spécial qui permettent une interface plus expressive/fine.

Je ne pense pas qu'aucune de ces solutions ne nécessite de modifications supplémentaires à Backbone autres que #3684, mais il y aurait un rôle intéressant pour une bibliothèque de décorateurs de backbone si/quand les décorateurs deviendraient standardisés.

J'aimerais avoir des commentaires sur la publication avant de la publier lundi/mardi.

@benmccormick J'ai pensé que les décorateurs sont évalués avant toute construction, merci pour la correction. Je mettrai à jour l'essentiel dans un instant. aussi : merci mille fois pour la mention dans l'article de blog :) Envoyez-moi un ping sur twitter lorsque vous le publiez ? :+1:

Nous pourrions utiliser la même syntaxe pour modelEvents et collectionEvents dans Marionette, mais pas pour les déclencheurs. Ceux-ci pourraient être exposés via un décorateur de classe (comme tagName et template dans votre article de blog), mais je pensais : ne pouvons-nous pas utiliser des propriétés statiques pour cela ? Ou cela ne fonctionne-t-il pas dans Backbone ?

Je comprends que les décorateurs sont toujours au stade 0, mais je pense qu'ils seront une excellente mise à niveau dans la façon dont nous écrivons les applications Backbone, en particulier les décorateurs de méthode sur un hachage d'événements, c'est le genre de style de programmation qui me fait préférer gulp au grognement aussi.

@StevenLangbroek voir ci-dessus pour une discussion sur les propriétés statiques.

La syntaxe telle qu'elle est actuellement spécifiée crée une propriété locale sur chaque instance plutôt que de l'ajouter au prototype. Ces propriétés sont ajoutées après l'exécution du super constructeur.

@benmccormick , le message a l'air bien et je pense qu'il explique bien les compromis avec chacune des options. À ce stade, j'aime beaucoup l'approche des décorateurs à usage spécial, et cela semble être la meilleure approche en supposant que les décorateurs soient intégrés à la spécification.

Le décorateur de @benmccormick devrait- _#extend avec le constructeur et non le prototype, puis la méthode _.instOrStaticVar @akre54 est-elle utilisée à la place de _#result ? Je me rends compte que ce serait un changement décisif, mais cela semble plus propre de cette façon à mon humble avis. Comme @ akre54 l'a souligné, les propriétés définies de cette manière sont des chaînes et des objets partagés par un

Je vais plus loin et crée des propriétés de classe ouvrière comme nous en avons besoin. Les propriétés de classe peuvent également être annotées et nous pouvons créer un décorateur spécial, qui attache la propriété décorée au prototype.

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

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

Voir Babel REPL avec exemple. Il repose sur des choses expérimentales, mais fonctionne.

@just-boris comme indiqué dans les commentaires de mon blog, le comportement que vous y voyez est un détail d'implémentation de la gestion par Babel des propriétés de classe et des spécifications des décorateurs. Son comportement n'est défini dans aucune proposition pour le moment. Si vous voulez faire les choses de cette façon, vous voudrez faire des problèmes ici et/ou ici pour faire des décorateurs sur les propriétés de classe un comportement standardisé. Sinon, ce que vous faites pourrait (et sera probablement) casser à tout moment.

@benmccormick wycats/javascript-decorators a déjà une définition supplémentaire concernant les initialiseurs de propriété .

La principale préoccupation des initialiseurs de propriétés est un descripteur habituel, ainsi que des méthodes de classe, afin que les décorateurs puissent également les envelopper. Je ne vois pas de raisons de s'inquiéter, alors que les spécifications de cette section restent inchangées

Ah très cool, je n'avais pas vu ça. Merci d'avoir fait remarquer cela.

Le lun 21 sept. 2015 à 11h29, Boris Serdiuk [email protected]
a écrit:

@benmccormick https://github.com/benmccormick
https://github.com/wycats/javascript-decorators a déjà des extras
définition concernant les initialiseurs de propriétés
https://github.com/wycats/javascript-decorators/blob/master/INITIALIZER_INTEROP.md
.

La principale préoccupation des initialiseurs de propriétés est un descripteur habituel,
ainsi que des méthodes de classe, afin que les décorateurs puissent également les envelopper. je ne vois pas
des raisons de s'inquiéter, tandis que les spécifications de cette section restent inchangées

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/jashkenas/backbone/issues/3560#issuecomment-142015454
.

Je voulais juste connaître les avantages/inconvénients de l'utilisation de https://github.com/typhonjs/backbone-es6 par rapport à la technique de la méthode suggérée par @benmccormick.

Au fait, merci @benmccormick pour cet excellent article de blog !

En plus de la proposition (#121) pull-request attachant ici la méthode properties en action https://github.com/dsheiko/backbone-abstract/tree/master/demo-es6/src/Js
Comme @akre54 l'a mentionné, Justin a déjà proposé une solution similaire (méthode preInitialize ). Alors que je l'utilisais déjà sur ma branche, cela me résout vraiment le problème. Semblaient également utiles dans TypeScript malgré qu'ils n'interdisent pas les propriétés de classe déclaratives.

PS preInitialize sonne plus général et donc mieux dans ce contexte. Bien que cela ressemble plus à preConstruct si nous appelons la méthode avant tous les travaux du constructeur

J'aimerais vraiment que nous voyions une nouvelle proposition de propriétés de classe qui les définit sur le prototype. Il semble que de nombreuses personnes impliquées dans la proposition s'inquiètent des implications, mais je trouve incroyablement incohérent que les méthodes de classe soient directement attachées au prototype, alors que la proposition de Jeffmo les place dans le constructeur.

S'ils avaient choisi d'attacher des propriétés directement au prototype, vous seriez en mesure de migrer à peu près n'importe quel code React/Backbone vers les classes ES2015.

super article de blog @benmccormick !! vais utiliser ces décorateurs dans mon projet

@benmccormick , j'ai inventé une autre façon de déclarer des classes avec des propriétés par défaut, jetez un œil : https://github.com/epicmiller/es2015-default-class-properties

Il s'exécute normalement dans n'importe quel environnement qui prend en charge nativement les classes, se transpile bien et semble _de loin_ plus agréable que de les définir dans le constructeur ou après la déclaration. Avec des propositions de décorateurs et de propriétés de classe à venir pour ES2016/ES2017, cela peut être plus un exercice académique qu'une solution à long terme pour Backbone, mais quelque chose comme ça est certainement une option viable si 2-3 ans est trop long d'un attendre.

Eh bien, le fait est que les propriétés de classe sont toujours à l'étape 1 dans le système d'étape de proposition Ecmascript. Je ne sais pas pourquoi, car cela semble être un cadeau en termes de "ce que l'utilisateur obtient". Bien sûr, je n'ai aucune idée du genre de choses que cela pourrait casser sous le capot à la fois en termes de syntaxe et en termes d'implémentations de référence.

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

En lisant ceci, je trouve que https://github.com/epicmiller/es2015-default-class-properties est une bonne approche. En essayant, j'ai réalisé que Backbone avait un support intégré pour cela. Par exemple:

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

Le code ci-dessus définira correctement MyModel.prototype.idAttribute. Remarquez que pour TypeScript, le fichier de déclaration doit être légèrement ajusté pour renvoyer une interface de fonction de constructeur, mais c'est un détail non pertinent pour les utilisateurs d'ES6...

@t-beckmann c'est une très bonne solution - semble lisible et nécessite des changements minimes. Merci!

Je me rends compte que ce fil dure maintenant 2 ans, mais c'est toujours l'un des meilleurs (et seulement) résultats lors de la recherche de classes Backbone et ES6, et j'ai pensé partager une solution potentielle utilisant les propriétés de classe mentionnées plusieurs fois ici .

Maintenant que les propriétés de classe sont à l' étape 2 et largement disponibles avec le préréglage babel, j'ai pensé y jeter un autre coup d'œil. Comme indiqué, le problème avec les propriétés d'instance/membre est qu'elles ne sont pas appliquées au prototype avant _after_ constructor() , mais la plupart des propriétés devant être définies sont utilisées dans le constructeur. Les propriétés statiques sont appliquées immédiatement, mais (par conception) ne sont pas copiées dans les instances de la classe.

Le shim suivant copie les propriétés statiques du constructeur sur l'instance avant d'exécuter le constructeur (création effective d'un nouveau constructeur, application des propriétés, puis exécution du constructeur d'origine). Bien qu'il s'agisse certainement d'un hack, je suis assez satisfait du résultat :

La cale :

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

Et puis en utilisation :

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

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

Je voulais juste le déposer ici au cas où cela aiderait quelqu'un d'autre, ou quelqu'un aurait une idée à ce sujet. Merci!

Obligatoire désolé de raviver un vieux problème.

Serait-il possible ou utile d'écrire un plugin babel qui transforme une déclaration de classe ES6 pour utiliser Backbone.*.extend({...}) ?

@enzious semble définitivement possible. C'est à vous de décider si cela en vaut la peine :)

La solution de @t-beckmann semble la plus simple. devrions-nous intégrer cela dans le backbone lui-même ?

Pour moi, cela ne semble pas correctement, ne serait-il pas plus approprié d'avoir une méthode qui définit l'idAttribute?

De plus, ce serait incroyable s'il y avait un support Promise. qui est une approche plus native que l'utilisation de jquery Deferred, que j'aimerais personnellement voir dépréciée dans Backbone.

L'histoire ici est encore très floue pour rafraîchir les applications Backbone héritées afin d'utiliser des outils et des fonctionnalités linguistiques modernes. Il est particulièrement décevant de voir des choses comme Symbol.iterator implémentées et non disponibles dans une version de production.

Pour ceux qui recherchent toujours des réponses plus claires à cette question, j'ajoute TypeScript à une application principale et j'ai trouvé la solution de ce commentaire la plus utile.

Jusqu'à présent, cela fonctionne assez bien, avec l'inconvénient d'avoir à annoter explicitement les propriétés passées par le décorateur plutôt que d'avoir une meilleure inférence.

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> {}

Exemple d'annotation de propriété explicite :

image

@raffomania , @jridgewell & Co., pour ce que ça vaut, mon équipe a contourné ce problème en ajoutant idAttribute au prototype en dehors de la classe.

La classe Exemple étend ParentExample {
// Classer les méthodes etc ici
}

x.Exemple = Exemple ;

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

@kamsci j'ai fait la même chose dans cette branche où j'ai converti Backbone en classes ES6

Backbone utilise _configuration_ au point que les objets de configuration sont _déclaratifs_. C'est bien mais ça ne va jamais jouer bien avec l'héritage. (Clonez la classe, puis configurez-la. Ce n'est pas de l'héritage.)

Si nous allons écrire un nouveau code en utilisant le backbone, il est normal de penser différemment. Couper et coller le code ES5, puis faire croire que ES6 ne fonctionne pas. Et alors?

Je n'ai aucun problème à tout transmettre via un objet de configuration. La façon dont nous exposons le contenu de cette configuration, ou la rendons plus facile à lire/travailler avec, est un problème à résoudre, pas à pleurer.

Personne ne veut exécuter un constructeur deux fois. C'est bête. Mais, le modèle de

Foo = BackboneThing.extend({LONG DECLARATIVE OBJECT LITTERAL}) est aussi laid qui aime les mères. Vous le faites tous depuis si longtemps que vous ne voyez pas à quel point c'est moche.

Pour info : j'ai un grand projet de marionnette et je voulais utiliser la syntaxe ES6. J'ai créé un transformateur jscodeshift qui traduit les déclarations d'extensions Backbone en classes ES6. Il fait de nombreuses hypothèses simplificatrices, mais peut toujours être utile pour certains d'entre vous, ne serait-ce que comme point de départ. Il suit la syntaxe proposée par @t-beckmann car j'ai rencontré des problèmes avec les décorateurs.
https://gist.github.com/maparent/83dfd65a37aaaabc4072b30b67d5a05d

Pour moi, il me semble qu'il y a un abus de langage étrange dans ce fil. Les 'propriétés statiques' à ES6 sont des propriétés sur le constructeur qui existent sur la classe sans instanciation (Class.extend par exemple). Dans ce fil de discussion, les « propriétés statiques » semblent faire référence à des attributs nommés sur le prototype avec une valeur « statique » (et non des getters ou des fonctions). Ai-je bien compris ?

Pour les propriétés de prototype avec une valeur statique, déclarer les valeurs de pré-initialisation Backbone en tant que valeurs de retour de fonction est une transition assez simple et fonctionne bien car _.result fonctionne comme prévu pour les valeurs par défaut, className, id, etc. D'autres propriétés d'instance semblent être bien déclarées à le haut de la fonction d'initialisation comme d'habitude. Ce problème semble se poser uniquement car dans les classes ES6, vous ne pouvez pas définir de propriétés de prototype avec une valeur statique pour le moment, uniquement des getters, des setters et des fonctions.

Dans tous les cas, les propriétés statiques du constructeur/classe (Class.extend) ne sont pas héritées dans le backbone comme elles le sont dans ES6. Le backbone copie les propriétés statiques de la classe dans la nouvelle classe/le nouveau constructeur à chaque fois lors de l'exécution de la fonction d'extension plutôt que de faire hériter ces propriétés comme le fait ES6. J'ai fait un pr pour corriger cela ici https://github.com/jashkenas/backbone/pull/4235

J'apprécierais quelques commentaires / retours, je ne sais pas si ça va casser quoi que ce soit, je l'ai pas mal testé et ça a l'air de bien fonctionner. Les classes de backbone héritent ensuite de Class.extend plutôt que de copier une référence à chaque nouveau constructeur.

Cette page vous a été utile?
0 / 5 - 0 notes