Backbone: Clases Backbone y ES6

Creado en 7 abr. 2015  ·  63Comentarios  ·  Fuente: jashkenas/backbone

Con los cambios finales a la especificación de la clase ES6 (detalles aquí ), ya no es posible usar las clases ES6 con Backbone sin hacer concesiones significativas en términos de sintaxis. He escrito una descripción completa de la situación aquí (asegúrese de hacer clic en los comentarios en la parte inferior para obtener una opción de mitigación adicional), pero esencialmente no hay forma de agregar propiedades a una instancia de una subclase antes de las subclases padres constructor en ejecución.

Así que esto:

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

ya no es válido en la especificación final de ES6. En su lugar, tiene efectivamente 3 opciones (no muy atractivas) si desea intentar que esto funcione:

Adjuntar todas las propiedades como funciones

Backbone permite esto, pero se siente tonto escribir algo como esto:

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

en comparación con la sintaxis extendida actual

Ejecute el constructor dos veces

No veo esto como una opción real debido a los problemas que causaría al ejecutar la inicialización por segunda vez con diferentes cids, etc.

Pasar todas las propiedades como opciones predeterminadas al constructor de la superclase

Esto fue sugerido por un comentarista en mi blog y es probablemente la opción actual más práctica. Se parece a esto:

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

Dado que todas estas opciones actuales implican compromisos claros en relación con la sintaxis actual de extensiones de Backbone, sería maravilloso si se pudiera desarrollar una solución mejor. No estoy totalmente seguro de cómo debería verse esto, pero una idea que me vino a la mente mientras escribía para mi blog fue la adición de una función de "propiedades" que generaría un hash de propiedades. Luego, el constructor podría ejecutar esa función y agregarla a la instancia antes del otro procesamiento realizado por el constructor.

change

Comentario más útil

Al leer esto, encuentro https://github.com/epicmiller/es2015-default-class-properties un buen enfoque. Cuando lo intenté, me di cuenta de que Backbone tenía soporte integrado para esto. Por ejemplo:

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

El código anterior configurará MyModel.prototype.idAttribute correctamente. Tenga en cuenta que para TypeScript, el archivo de declaración debe ajustarse ligeramente para devolver una interfaz de función de constructor, pero ese es un detalle irrelevante para los usuarios de ES6 ...

Todos 63 comentarios

Sí, esto es definitivamente un fastidio. Gracias por hacer el trabajo de campo.

Supongo que la moraleja de la historia es no usar las clases ES6 con Backbone, al menos hasta que llegue el soporte de propiedad estática. De las opciones de respaldo que propuso, mi solución preferida es definir las cadenas / objetos como valores de retorno. Una parte clave del diseño de la API de Backbone está en estos objetos y cadenas de prototipos compartidos, y ensuciaría la API para requerir que los desarrolladores asignen cada propiedad a la instancia en el constructor (sin mencionar que desperdicia memoria).

Aparte de la coherencia, ¿hay alguna razón para usar la palabra clave class con Backbone sobre extend ?

Gran publicación de blog. Me preguntaba cómo jugarían juntas las clases ES6 y Backbone. En cuanto a sus soluciones:

  1. Adjunte todas las propiedades como funciones : no estoy muy en contra de esto. No es tan limpio como colocar el objeto directamente en el prototipo, pero he visto un montón de código tropezar en objetos prototipo mutantes. De esta manera es inmune, por lo que creo que ES6 eligió no incluir propiedades de clase.
  2. Pase todas las propiedades como opciones predeterminadas : ¿No es así como haría algo en un lenguaje más clásico? Siento que esta es una solución aún menos limpia que la anterior.
  3. Ejecute el constructor dos veces : Ick.

Supongo que la moraleja de la historia es no usar las clases ES6 con Backbone, al menos hasta que llegue el soporte de propiedad estática.

Incluso las propiedades de clase vienen después de la llamada super() . :decepcionado:

Aparte de la coherencia, ¿hay alguna razón para usar la palabra clave class con Backbone sobre extender?

Abordé esto en la publicación del blog. ¿Prácticamente? No. En teoría, permitiría a Backbone reducir el código y los conceptos adicionales a largo plazo, pero de manera realista pasarán al menos unos años antes de que las clases de ES6 sean ampliamente compatibles en todos los navegadores relevantes sin transpilar, y la reducción de código sería lo siguiente. a nada.

Pero no subestimes el aspecto de la coherencia. Si esto se convierte en "la forma" de hacer programación orientada a objetos en JavaScript (parece probable dada la estandarización en esto de Ember / Angular / React / Typecript / Aurelia, etc.), Backbone que no lo use será una curva de aprendizaje adicional para la biblioteca en relación con otras opciones. Especialmente para desarrolladores junior. No estoy seguro de que eso necesariamente merezca un cambio. Pero no es sólo por la coherencia pedante del "duende de las mentes pequeñas".

Estoy de acuerdo con @ akre54 y @jridgewell en que el

ES7 tendrá las propiedades de clase correctas, supongo que https://gist.github.com/jeffmo/054df782c05639da2adb

La propuesta ES7 es solo eso, una propuesta muy temprana impulsada por la comunidad. No está del todo claro que en realidad alguna vez sea parte de una especificación oficial. Las implementaciones actuales hacen que se agreguen propiedades a la instancia DESPUÉS de que se ejecuta el constructor, por lo que no ayuda con Backbone. (vea el enlace de jridgewell arriba o pruébelo usted mismo con Babel 5.0.0)

@jridgewell Me refería a esta parte de la publicación de

Los desarrolladores de React han notado los mismos problemas con los inicializadores de propiedades que encuentran los usuarios de Backbone. Como parte de la versión 0.13 de React, admiten una sintaxis de inicialización de propiedad especial para clases, que eventualmente puede estandarizarse. Hay más información sobre eso en este hilo de ESDiscuss . Este estándar aún se está elaborando, pero hay una versión de soporte experimental disponible en Babel 5.0.0. Desafortunadamente, esa versión define las propiedades de la clase como instanciadas después de que se ejecuta el constructor de la superclase, por lo que esto no resuelve los problemas de Backbone aquí.

Ver, por ejemplo, wycats 'js-decorators strawman o la propuesta de clases de armonía original (reemplazada).

Podría sugerir que usemos captadores con propiedades de clase:

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

Como último recurso absoluto, podríamos verificar, por ejemplo, los accesorios estáticos con un ayudante a 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;
}

Sí, pero:

Desafortunadamente, esa versión define las propiedades de la clase como instanciadas después de que se ejecuta el constructor de la superclase, por lo que esto no resuelve los problemas de Backbone aquí.

Entonces, ES5 hizo:

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

Nuestro elemento aún estaría construido antes de que obtengamos un cambio para establecer la variable de instancia.

Ver, por ejemplo, wycats 'js-decorators strawman ...

¿Puede explicar cómo se aplicarían los decoradores?

Podría sugerir que usemos captadores con propiedades de clase:

: +1 :. Veo que es el mismo barco que adjunta todas las propiedades como funciones . No tan limpio como lo que tenemos actualmente, pero perfectamente aceptable y a prueba de mutaciones.

Como último recurso absoluto, podríamos comprobar, por ejemplo, los accesorios estáticos con un ayudante a la _.result:

Eso podría ser interesante ...

Podrías hacerlo:

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

@thejameskyle Esa es la Pasar todas las propiedades como opciones predeterminadas a la opción de

En lugar de confiar en super() para configurar la clase, simplemente podría tener una función init() o algo así.

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? Eso generará un error de inmediato con la especificación final de la clase ES6

En una clase derivada, debe llamar a super () antes de poder usar esto

Incluso si funcionó, nunca llamará al constructor Backbone y no obtendrá su código de inicialización.

Vea este enlace de mi primera publicación: http://www.2ality.com/2015/02/es6-classes-final.html

@milesj : La cosa es que debes llamar a super() antes de configurar this.tagName o similar. Y, dado que aseguramos un elemento en el constructor de la Vista, ya hemos creado un elemento antes de que establezcamos this.tagName .

@milesj eso todavía no está permitido cuando está subclasificando.

@jridgewell Oh, lo siento, me perdí eso. Parece la opción más natural. Hablé con jeffmo y sebmck sobre esto.

Para darles algo de historia de fondo, el razonamiento es que para admitir la extensión de tipos nativos (es decir, Array) this no se determina hasta que se llama al método super() . De lo contrario, se encontrará con un problema de inicialización en el DOM (y presumiblemente en otros lugares).

@jridgewell @thejameskyle Luego, simplemente llame a super () primero (ejemplo actualizado). Realmente no veo el problema aquí, ya que hice lo mismo en mis clases de ES6. Simplemente mueva la lógica del constructor de vistas al método init() .

Eso es mucho código muy caro para ejecutar dos veces.

@milesj , ¿leíste la publicación original del blog? Ejecutar super first significa que las propiedades no se procesan. Consulte aquí para obtener una explicación completa en profundidad: http://benmccormick.org/2015/04/07/es6-classes-and-backbone-js/

Sí, lo he leído y todavía tengo curiosidad por saber por qué esto no es una solución. Todo el mundo sigue hablando de la necesidad de llamar al constructor de vistas, pero ese no es necesariamente el caso. ¿Por qué algo como lo siguiente no es una solución (aunque sea un poco artificial)?

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

Supongo que debido a la compatibilidad con versiones anteriores que no son de ES6.

Entonces la clase predeterminada View no funcionaría ya que el constructor nunca llama a #setup . Y, forzar una llamada de subclase a cualquier otra cosa que no sea super() va a ser muy molesto.

Ese es un problema con el que tienen que lidiar todas las clases de ES6, no solo Backbone. Personalmente lo resolví usando la especificación de propiedades de la clase Babel ES7.

@milesj Como se indicó anteriormente, las propiedades de la clase ES7 no resuelven este problema ya que no se instancian hasta el final del constructor.

Hablé con jeffmo y sebmck sobre hacer esto:

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

Deseando:

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

Pero eso todavía no soluciona el problema aquí y conduce a una inconsistencia:

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

Ese es un problema con el que tienen que lidiar todas las clases de ES6, no solo Backbone.

¿Hm?

Personalmente lo resolví usando la especificación de propiedades de la clase Babel ES7.

Vas a tener muchos elementos DIV sin className s. Vea el último punto de https://github.com/jashkenas/backbone/issues/3560#issuecomment -90739676, https://github.com/jashkenas/backbone/issues/3560#issuecomment -91601515 y https: // github .com / jashkenas / backbone / issues / 3560 # issuecomment -98827719.

Veo. En ese caso, sugiero ir con la opción "Pasar todas las propiedades como opciones predeterminadas al constructor de la superclase", o la última línea sobre la creación de un método de "propiedades" (que no toca al constructor).

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

Hice algo similar en Toolkit, que se puede ver aquí: https://github.com/titon/toolkit/issues/107

Hola.

Si entiendo correctamente la discusión aquí, los desarrolladores de Backbone están discutiendo soluciones y mejores prácticas, pero ¿no tienen la intención de realizar cambios en el núcleo de BB para abordar este problema? (No estoy sugiriendo que deberían hacerlo, ni tengo idea de cuáles podrían ser esos cambios). En otras palabras, ¿la sugerencia de utilizar todas las propiedades como funciones o los getters es la última palabra sobre el tema? Gracias.

@gotofritz Estamos discutiendo soluciones porque la solución de ES6 de obligar a todas las propiedades a vivir en instancias no escala. El sistema de clases de Backbone está haciendo lo correcto aquí.

Existe cierta discusión sobre la adición de propiedades de prototipos estáticos a las clases de ES7, pero hasta ahora nada concreto. Mientras tanto, diría que se quede con extend Backbone.

Gracias. Intentaré las clases de ES6 un poco más ... :-)

Para el beneficio de cualquier otra persona que se encuentre con esto, en la práctica encuentro mejor la opción "Pasar todas las propiedades como opciones predeterminadas al constructor de la superclase"; por ejemplo, nuestra aplicación tiene rutas dinámicas (localizadas) que deben pasarse en el momento de la instanciación, y tener un método de rutas () simplemente no funciona. Considerando que lo siguiente

class Router extends Backbone.Router {

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

Acabo de ver esto y creo que ambas soluciones no funcionan para la propiedad idAttribute que tiene un modelo. Un método no funcionará ya que Backbone usa model.idAttribute para acceder a la propiedad; Y el constructor del modelo no parece admitir la adición de propiedades como opciones por completo.

Creo que ambas soluciones no funcionan para la propiedad idAttribute

Excelente captura, trabajaré en un PR que aborde esto. Mientras tanto, puede usar la notación getter para proporcionar idAttribute (y cidPrefix ):

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

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

Un método no funcionará ya que Backbone usa model.idAttribute para acceder a la propiedad

get idAttribute() { return '_id'; } es un método getter, al que se accede como una propiedad normal. this.idAttribute === '_id'; .

Esto está empezando a parecer que se requiere una reescritura importante. ¿Quizás Backbone v2?

Esto está empezando a parecer que se requiere una reescritura importante. ¿Quizás Backbone v2?

Para nada, ya admitimos la subclasificación de ES6 (con la excepción de los modelos ). Creo que sería interesante si alguien explorara la sugerencia de propiedad estática de @ akre54 , pero incluso eso no es necesario con las dos soluciones en la publicación original.

@jridgewell , ¡muchas gracias por la rápida solución!

Los decoradores se mencionaron anteriormente en este hilo (específicamente la propuesta de Yehuda Katz ), y no se resolvió si eso resolvería este problema.

Estaba jugando con ellos como se propuso, y puedes escribir un decorador como este:

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

y luego el ejemplo que hemos estado usando se puede escribir así

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

Esto parece funcionar bien para mí. El decorador se aplica a la clase antes de que se ejecute el constructor de la clase. Esta es solo una versión declarativa de decir

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 realidad, no lo he probado, pero probablemente podría hacer que toda la función de extensión de la columna vertebral sea el decorador si quisiera accesorios tanto estáticos como prototipos.

Desafortunadamente, esta es solo una propuesta por ahora, pero Babel la respalda detrás de una bandera experimental, por lo que si la gente se siente aventurera, es una posible solución aquí.

@benmccormick , la técnica del decorador me funciona bien. Aparte de que esto es solo una propuesta por ahora, ¿hay otras preocupaciones sobre seguir este enfoque?

@andrewrota Estoy literalmente escribiendo una publicación de blog siguiendo este tema en este momento (estaba leyendo este hilo cuando comentaste). Ese es un gran "otro que", pero personalmente no veo ninguno. De hecho, creo que podemos hacerlo mejor que lo que describí anteriormente, y crear algunas interfaces nuevas y agradables para Backbone con decoradores.

Vea esta esencia de @StevenLangbroek que me hizo pensar en esto originalmente: https://gist.github.com/StevenLangbroek/6bd28d8201839434b843

Aquí hay una vista previa de la publicación de seguimiento que estoy publicando: http://benmccormick.org/2015/07/06/backbone-and-es6-classes-revisited/ Actualizado con enlace permanente ahora

En algún momento, se moverá a una URL permanente a principios de esta semana. Pero el resumen básico de este hilo y lo que he aprendido es:

Hay 3 enfoques para hacer que las propiedades de Backbone funcionen con la especificación de clases ES6 actual (los 2 primeros necesitan # 3684 para ser considerados totalmente compatibles):

  1. Pasar todas las propiedades al super en el constructor
  2. Trate todas las propiedades como métodos
  3. Agregue propiedades directamente al prototipo después de que se haya declarado una clase

Sigo considerando que todos estos factores limitan la expresividad en un grado u otro. Pero creo que el problema se resolverá más o menos si los decoradores se convierten en una especificación oficial. Con los decoradores hay 2 opciones más.

  1. Agregue un decorador de accesorios que tome los accesorios en la parte superior de la clase y los agregue al prototipo
  2. Cree varios decoradores de propósito especial que permitan una interfaz más expresiva / fina.

No creo que ninguna de estas soluciones requiera modificaciones adicionales a Backbone que no sean # 3684, pero habría un papel interesante para una biblioteca de decoradores de backbone si / cuando los decoradores se estandaricen.

Me encantaría recibir comentarios sobre la publicación antes de publicarla el lunes / martes.

@benmccormick Supuse que los decoradores son evaluados antes de que ocurra cualquier construcción, gracias por la corrección. Actualizaré la esencia en un momento. también: un millón de gracias por la mención en la publicación del blog :) ¿enviarme un ping en twitter cuando lo publiques? : +1:

Podríamos usar la misma sintaxis para modelEvents y collectionEvents en Marionette, pero no para los disparadores. Esos podrían exponerse a través de un decorador de clases (como tagName y plantilla en la publicación de su blog), pero estaba pensando: ¿no podemos usar propiedades estáticas para esto? ¿O eso no funciona en Backbone?

Entiendo que los decoradores todavía están en la etapa 0, pero creo que serán una gran mejora en la forma en que escribimos las aplicaciones Backbone, especialmente los decoradores de métodos sobre un hash de eventos, es el tipo de estilo de programación que me hace preferir gulp sobre gruñido también.

@StevenLangbroek vea arriba para una discusión sobre las propiedades estáticas.

La sintaxis tal como se especifica actualmente crea una propiedad local en cada instancia en lugar de agregarla al prototipo. Esas propiedades se agregan después de que se ejecuta el superconstructor.

@benmccormick , la publicación se ve bien y creo que hace un buen trabajo al explicar las compensaciones con cada una de las opciones. En este punto, me gusta mucho el enfoque de los decoradores de propósito especial, y parece ser el mejor enfoque asumiendo que los decoradores entran en la especificación.

¿ Debería el decorador de @benmccormick llamar a _#extend con el constructor no el prototipo y luego se usa el método _.instOrStaticVar @ akre54 en lugar de _#result ? Me doy cuenta de que sería un cambio radical, pero parece más limpio de esa manera en mi humilde opinión. Como @ akre54 señaló que las propiedades definidas de esa manera son cadenas y objetos compartidos por

Voy más allá y hago que las propiedades de las clases funcionen como sea necesario. Las propiedades de la clase también se pueden anotar y podemos crear un decorador especial, que adjunta la propiedad decorada al prototipo.

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

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

Consulte Babel REPL con un ejemplo. Se basa en cosas experimentales, pero funciona.

@ just-boris como se discutió en los comentarios de mi blog, el comportamiento que está viendo allí es un detalle de implementación del manejo de Babel de las propiedades de la clase y las especificaciones de los decoradores. Su comportamiento no está definido en ninguna propuesta en este momento. Si desea hacer las cosas de esa manera, querrá crear problemas aquí y / o aquí para convertir a los decoradores en las propiedades de la clase en un comportamiento estandarizado. De lo contrario, lo que está haciendo podría (y probablemente lo hará) romperse en cualquier momento.

@benmccormick wycats / javascript-decorators ya tiene una definición adicional con respecto a los inicializadores de propiedades .

La principal preocupación que tienen los inicializadores de propiedades es un descriptor habitual, así como los métodos de clase, por lo que los decoradores también pueden envolverlos. No veo motivos para preocuparme, mientras que la especificación en esa sección permanece sin cambios

Ah, muy bien, no había visto eso. Gracias por señalar eso.

El lunes 21 de septiembre de 2015 a las 11:29 a. M., Boris Serdiuk [email protected]
escribió:

@benmccormick https://github.com/benmccormick
https://github.com/wycats/javascript-decorators ya tiene extra
definición con respecto a los inicializadores de propiedad
https://github.com/wycats/javascript-decorators/blob/master/INITIALIZER_INTEROP.md
.

La principal preocupación que tienen los inicializadores de propiedad es un descriptor habitual,
así como los métodos de clase, para que los decoradores también puedan envolverlos. No veo
razones para preocuparse, mientras que las especificaciones en esa sección permanecen sin cambios

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/jashkenas/backbone/issues/3560#issuecomment -142015454
.

Solo quería saber los pros / contras de usar https://github.com/typhonjs/backbone-es6 frente a la técnica del método sugerida por @benmccormick.

Por cierto, ¡gracias @benmccormick por la excelente publicación del blog!

Además de la solicitud de extracción de la propuesta (# 121) adjuntando aquí el método properties en acción https://github.com/dsheiko/backbone-abstract/tree/master/demo-es6/src/Js
Como mencionó @ akre54, Justin ya ha propuesto una solución similar (método preInitialize ). Aunque ya lo estoy usando en mi rama, realmente me resuelve el problema. También pareció ser útil en TypeScript a pesar de que no prohíbe las propiedades de clase declarativas.

PS preInitialize suena más general y, por lo tanto, mejor en este contexto. Aunque, es más como preConstruct si llamamos al método antes de todos los trabajos del constructor

Realmente desearía ver una nueva propuesta de propiedades de clase que las coloque en el prototipo. Parece que muchos involucrados con la propuesta están preocupados por las implicaciones, pero me parece increíblemente inconsistente que los métodos de clase se adjunten directamente al prototipo, mientras que la propuesta de jeffmo los coloca en el constructor.

Si hubieran ido adjuntando propiedades directamente al prototipo, podría migrar prácticamente cualquier código de React / Backbone a las clases de ES2015.

increíble publicación de blog @benmccormick !! voy a usar esos decoradores en mi proyecto

@benmccormick , me https://github.com/epicmiller/es2015-default-class-properties

Se ejecuta normalmente en cualquier entorno que admita clases de forma nativa, se transpila bien y se ve _mucho_ mejor que definirlas en el constructor o después de la declaración. Con propuestas para decoradores y propiedades de clase en proceso para ES2016 / ES2017, esto puede ser más un ejercicio académico que la solución a largo plazo para Backbone, pero algo como esto es definitivamente una opción viable si 2-3 años es demasiado tiempo. Espere.

Bueno, la cosa es que Class Properties todavía se encuentra en la etapa 1 en el sistema de etapas de propuesta de Ecmascript. No tengo idea de por qué, ya que parece un regalo en términos de "lo que obtiene el usuario". Por supuesto, no tengo idea de qué tipo de cosas podría romper bajo el capó, tanto sintácticamente como en términos de implementaciones de referencia.

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

Al leer esto, encuentro https://github.com/epicmiller/es2015-default-class-properties un buen enfoque. Cuando lo intenté, me di cuenta de que Backbone tenía soporte integrado para esto. Por ejemplo:

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

El código anterior configurará MyModel.prototype.idAttribute correctamente. Tenga en cuenta que para TypeScript, el archivo de declaración debe ajustarse ligeramente para devolver una interfaz de función de constructor, pero ese es un detalle irrelevante para los usuarios de ES6 ...

@ t-beckmann es una solución bastante buena: parece legible y requiere cambios mínimos. ¡Gracias!

Me doy cuenta de que este hilo dura 2 años, pero sigue siendo uno de los mejores (y únicos) resultados al buscar clases Backbone y ES6, y pensé en compartir una solución potencial haciendo uso de las propiedades de clase mencionadas varias veces aquí. .

Ahora que las propiedades de la clase están en la Etapa 2 y están ampliamente disponibles con el ajuste preestablecido de babel, pensé en darle otra mirada. Como se indicó, el problema con las propiedades de instancia / miembro es que no se aplican al prototipo hasta _después_ constructor() , pero muchas de las propiedades que deben establecerse se utilizan dentro del constructor. Las propiedades estáticas se aplican inmediatamente, pero (por diseño) no se copian en instancias de la clase.

La siguiente corrección copia las propiedades estáticas del constructor en la instancia antes de ejecutar el constructor (creando efectivamente un nuevo constructor, aplicando las propiedades y luego ejecutando el constructor original). Si bien definitivamente es un truco, estoy bastante satisfecho con el resultado:

La calza:

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

Y luego en uso:

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

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

Solo quería dejarlo aquí en caso de que ayude a alguien más, o alguien tenga alguna idea al respecto. ¡Gracias!

Obligatorio perdón por revivir un tema antiguo.

¿Sería posible o valdría la pena escribir un complemento de babel que transforme una declaración de clase ES6 para usar Backbone. *. Extend ({...})?

@enzious definitivamente parece posible. Si vale la pena, depende de ti :)

La solución de @ t-beckmann parece la más sencilla. ¿Deberíamos integrar eso en la propia columna vertebral?

Para mí, no se ve bien, ¿no sería más adecuado tener un método que establezca el atributo idAttribute?

Además, sería increíble si hubiera soporte de Promise. que es un enfoque más nativo que usar jquery Deferred, que personalmente me encantaría ver obsoleto dentro de Backbone.

La historia aquí todavía es muy poco clara para actualizar las aplicaciones Backbone heredadas para utilizar herramientas modernas y características de lenguaje. Es especialmente decepcionante ver cosas como Symbol.iterator implementadas y no disponibles en una versión de producción.

Para aquellos que aún buscan respuestas más claras a esta pregunta, estoy agregando TypeScript a una aplicación de red troncal y encontré que la solución de este comentario es más útil.

Hasta ahora, está funcionando bastante bien, con el inconveniente de tener que anotar explícitamente las propiedades pasadas a través del decorador en lugar de tener una mejor inferencia.

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

Ejemplo de anotación de propiedad explícita:

image

@raffomania , @jridgewell & Co., por lo que vale, mi equipo solucionó este problema agregando idAttribute al prototipo fuera de la clase.

class Example extiende ParentExample {
// Métodos de clase, etc. aquí
}

x.Example = Ejemplo;

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

@kamsci hice lo mismo en esta rama donde convertí Backbone a clases ES6

Backbone usa _configuration_ hasta el punto de que los objetos de configuración son _declarative_. Esto es bueno, pero nunca va a ser bueno con la herencia. (Clone la clase, luego configúrela. Eso no es herencia).

Si vamos a escribir código nuevo usando backbone, está bien pensar de manera diferente. Cortar y pegar el código ES5 y luego hacer que parezca que ES6 no funciona. ¿Y qué?

No tengo ningún problema para pasar todo a través de un objeto de configuración. La forma en que exponemos el contenido de esa configuración, o lo hacemos más fácil de leer / trabajar, es un problema a resolver, no a llorar.

Nadie quiere ejecutar un constructor dos veces. Eso es tonto. Pero, el patrón de

Foo = BackboneThing.extend ({LARGO OBJETO DECLARATIVO LITERAL}) también es feo y amante de las madres. Todos lo han estado haciendo tanto tiempo que no ven lo feo que es.

FYI: Tengo un gran proyecto de Marionette y quería usar la sintaxis de ES6. Creé un transformador jscodeshift que traduce las declaraciones de extensiones de Backbone en clases de ES6. Hace muchas suposiciones simplificadoras, pero aún puede ser útil para algunos de ustedes, aunque solo sea como un punto de partida. Sigue la sintaxis propuesta por @ t-beckmann cuando encontré problemas con los decoradores.
https://gist.github.com/maparent/83dfd65a37aaaabc4072b30b67d5a05d

A mí me parece un nombre poco apropiado en este hilo. Las 'propiedades estáticas' de ES6 son propiedades en el constructor que existen en la Clase sin instanciación (Class.extend, por ejemplo). En este hilo, 'propiedades estáticas' parece referirse a atributos con nombre en el prototipo con un valor 'estático' (no captadores o funciones). ¿Tengo eso bien?

Para las propiedades de prototipo con un valor estático, declarar los valores de inicialización previa de Backbone como valores de retorno de la función es una transición bastante sencilla y funciona bien, ya que _.result funciona como se esperaba para los valores predeterminados, className, id, etc. Otras propiedades de instancia parecen estar bien declaradas en la parte superior de la función de inicialización con normalidad. Este problema parece solo surgir ya que en las clases de ES6 no se pueden definir propiedades de prototipo con un valor estático en la actualidad, solo getters, setters y funciones.

De cualquier manera, las propiedades estáticas de constructor / clase (Class.extend) no se heredan en la red troncal como en ES6. Backbone copia las propiedades estáticas de la clase a la nueva clase / constructor cada vez que se realiza la función de extensión en lugar de que estas propiedades se hereden como lo hace ES6. He hecho un PR para solucionarlo aquí https://github.com/jashkenas/backbone/pull/4235

Agradecería algunos comentarios / retroalimentación, no estoy seguro de si romperá algo, lo he probado bastante y parece funcionar bien. Las clases de backbone heredan Class.extend después en lugar de copiar una referencia a cada nuevo constructor.

¿Fue útil esta página
0 / 5 - 0 calificaciones