Backbone: Backbone e classes ES6

Criado em 7 abr. 2015  ·  63Comentários  ·  Fonte: jashkenas/backbone

Com as alterações finais nas especificações de classe ES6 (detalhes aqui ), não é mais possível usar classes ES6 com Backbone sem fazer compromissos significativos em termos de sintaxe. Eu escrevi uma descrição completa da situação aqui (certifique-se de clicar nos comentários na parte inferior para obter uma opção atenuante adicional), mas essencialmente não há como adicionar propriedades a uma instância de uma subclasse antes dos pais das subclasses construtor sendo executado.

Então, é isso:

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ão é mais válido na especificação ES6 final. Em vez disso, você efetivamente tem 3 opções (não muito atraentes) se quiser tentar fazer isso funcionar:

Anexe todas as propriedades como funções

O backbone permite isso, mas parece idiota escrever algo assim:

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

em comparação com a sintaxe extends atual

Execute o construtor duas vezes

Não vejo isso como uma opção real devido aos problemas que causaria ao executar uma segunda inicialização com CIDs diferentes, etc.

Passe todas as propriedades como opções padrão para o construtor da superclasse

Isso foi sugerido por um comentarista em meu blog e é provavelmente a opção atual mais prática. É mais ou menos assim:

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

Uma vez que todas essas opções atuais envolvem compromissos claros em relação à sintaxe de extensões do Backbone atual, seria maravilhoso se uma solução melhor pudesse ser desenvolvida. Não tenho certeza de como isso deve ser, mas uma ideia que me veio à mente enquanto eu escrevia para meu blog foi a adição de uma função de "propriedades" que geraria um hash de propriedades. O construtor pode então executar essa função e adicioná-los à instância antes do outro processamento feito pelo construtor.

change

Comentários muito úteis

Lendo isso, acho https://github.com/epicmiller/es2015-default-class-properties uma boa abordagem. Ao tentar percebi que o Backbone tinha suporte embutido para isso. Por exemplo:

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

O código acima definirá o MyModel.prototype.idAttribute corretamente. Observe, para TypeScript, o arquivo de declaração precisa ser ligeiramente ajustado para retornar uma interface de função do construtor, mas isso é um detalhe irrelevante para usuários ES6 ...

Todos 63 comentários

Sim, isso é definitivamente uma chatice. Obrigado por fazer o trabalho braçal.

Eu acho que a moral da história é não usar classes ES6 com Backbone, pelo menos até que o suporte de propriedade estática chegue. Das opções de fallback que você propôs, minha solução preferida é definir as strings / objetos como valores de retorno. Uma parte importante do design da API do Backbone está nessas strings e objetos compartilhados por protótipo, e isso sujaria a API para exigir que os desenvolvedores atribuam cada propriedade à instância no construtor (para não mencionar o desperdício de memória).

Além da consistência, há alguma razão para usar a palavra-chave class com Backbone acima de extend ?

Excelente postagem no blog. Eu estava me perguntando como as classes ES6 e Backbone funcionariam juntas. Quanto às suas soluções:

  1. Anexe todas as propriedades como funções : Eu não sou
  2. Passe todas as propriedades como opções padrão : Não é assim que você faria algo em uma linguagem mais clássica? Eu sinto que esta é uma solução ainda menos limpa do que a anterior.
  3. Execute o construtor duas vezes : Ick.

Eu acho que a moral da história é não usar classes ES6 com Backbone, pelo menos até que o suporte de propriedade estática chegue.

Mesmo as propriedades da classe vêm após a chamada super() . : desapontado:

Além da consistência, há alguma razão para usar a palavra-chave class com o Backbone over extend?

Abordei isso na postagem do blog. Praticamente? Não. Em teoria, isso permitiria ao Backbone, a longo prazo, reduzir o código e os conceitos adicionais, mas, realisticamente, ocorrerá pelo menos alguns anos antes que as classes ES6 sejam amplamente suportadas em todos os navegadores relevantes sem transpilar, e a redução do código seria a próxima para nada.

Mas não subestime o aspecto da consistência. Se isso se tornar "a maneira" de fazer programação Orientada a Objetos em JavaScript (parece provável dada a padronização disso em Ember / Angular / React / Typescript / Aurelia etc), o Backbone que não o usa será uma curva de aprendizado adicional para a biblioteca em relação a outras opções. Especialmente para desenvolvedores Junior. Não tenho certeza se isso necessariamente merece uma mudança. Mas não é apenas para a consistência pedante de "hobgoblin de mentes pequenas".

Eu concordo com @ akre54 e @jridgewell que a abordagem "anexar todas as propriedades como funções" é provavelmente a melhor das opções propostas. FWIW, eu me lembro que quando eu estava originalmente aprendendo backbone como um parente recém-chegado, fiquei um pouco confuso com essas propriedades "estáticas" e como elas deveriam ser usadas.

ES7 terá propriedades de classe corretas, eu acho que https://gist.github.com/jeffmo/054df782c05639da2adb

A proposta ES7 é apenas isso, uma proposta inicial voltada para a comunidade. Nada claro se realmente alguma vez fará parte de uma especificação oficial. As implementações atuais fazem com que as propriedades sejam adicionadas à instância APÓS a execução do construtor, portanto, isso não ajuda com o Backbone. (veja o link de jridgewell acima ou tente você mesmo com o Babel 5.0.0)

@jridgewell Eu estava me referindo a esta parte da postagem de

Os desenvolvedores do React observaram os mesmos problemas com inicializadores de propriedade que os usuários do Backbone encontram. Como parte da versão 0.13 do React, eles oferecem suporte a uma sintaxe de inicialização de propriedade especial para classes, que podem eventualmente ser padronizadas. Há mais informações sobre isso neste tópico ESDiscuss . Este padrão ainda está sendo desenvolvido, mas uma versão de suporte experimental está disponível no Babel 5.0.0. Infelizmente, essa versão define as propriedades da classe como sendo instanciadas depois que o construtor da superclasse é executado, portanto, isso não resolve os problemas do Backbone aqui.

Veja, por exemplo, o espantalho js-decorators dos wycats ou a proposta de classes de harmonia original (substituída).

Posso sugerir que usemos getters com propriedades de classe:

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

Como último recurso absoluto, poderíamos verificar por exemplo ou adereços estáticos com um auxiliar 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;
}

Sim, mas:

Infelizmente, essa versão define as propriedades da classe como sendo instanciadas depois que o construtor da superclasse é executado, portanto, isso não resolve os problemas do Backbone aqui.

Então, 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
});

Nosso elemento ainda seria construído antes de termos uma alteração para definir a variável de instância.

Veja, por exemplo, o espantalho js-decorators dos wycats ...

Você pode explicar como os decoradores se aplicariam?

Posso sugerir que usemos getters com propriedades de classe:

: +1 :. Eu vejo isso como o mesmo barco que anexa todas as propriedades como funções . Não tão limpo quanto o que temos atualmente, mas perfeitamente aceitável e à prova de mutação.

Como último recurso absoluto, poderíamos verificar por exemplo ou adereços estáticos com um auxiliar a la _.result:

Isso pode ser interessante ...

Você poderia fazer:

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

@thejameskyle Essa é a opção Passar todas as propriedades como opções padrão para a opção do

Em vez de depender de super() para configurar a classe, você poderia simplesmente ter uma função init() ou algo assim.

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? Isso irá gerar um erro imediatamente com a especificação final da classe ES6

Em uma classe derivada, você deve chamar super () antes de usar este

Mesmo que funcione, você nunca está realmente chamando o construtor do Backbone e não obterá seu código de inicialização.

Veja este link da minha primeira postagem: http://www.2ality.com/2015/02/es6-classes-final.html

@milesj : super() antes de definir this.tagName ou algo parecido. E, como garantimos um elemento no construtor da View, já criamos um elemento antes de definir this.tagName .

@milesj isso ainda não é permitido quando você está

@jridgewell Oh, desculpe, eu perdi isso. Parece a opção mais natural. Falei com jeffmo e sebmck sobre isso.

Para dar a vocês uma história de fundo, o raciocínio é porque, para suportar a extensão de tipos nativos (ou seja, Array), this não é determinado até que você chame o método super() . Caso contrário, você terá problemas de inicialização no DOM (e provavelmente em outros lugares).

@jridgewell @thejameskyle Então simplesmente chame super () primeiro (exemplo atualizado). Eu realmente não vejo o problema aqui, pois fiz a mesma coisa em minhas aulas de ES6. Basta mover a lógica do construtor de visualizações para o método init() .

Isso é um monte de muito caro código seja executado duas vezes.

@milesj você leu a postagem original do blog? Executar superprimeiro significa que as propriedades não são processadas. Veja aqui uma explicação completa em profundidade: http://benmccormick.org/2015/04/07/es6-classes-and-backbone-js/

Sim, eu li e ainda estou curioso para saber por que isso não é uma solução. Todo mundo fica falando sobre o construtor de visualizações que precisa ser chamado, mas esse não é necessariamente o caso. Por que algo como o seguinte não é uma solução (embora um pouco 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);
    }
}

Eu estou supondo por causa da compatibilidade com versões anteriores de não-ES6?

Então, a classe View padrão não funcionaria, pois o construtor nunca chama #setup . E, forçar uma chamada de subclasse qualquer coisa diferente de super() vai ser super irritante.

Esse é um problema com o qual todas as classes ES6 precisam lidar, não apenas o Backbone. Eu pessoalmente resolvi isso usando a especificação de propriedades de classe Babel ES7.

@milesj Conforme declarado antes, as propriedades da classe ES7 não resolvem esse problema, pois não são instanciadas até o final do construtor.

Falei com jeffmo e sebmck sobre como fazer isso:

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

Desugaring para:

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

Mas isso ainda não resolve o problema aqui e leva à inconsistência:

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

Esse é um problema com o qual todas as classes ES6 precisam lidar, não apenas o Backbone.

Hm?

Eu pessoalmente resolvi isso usando a especificação de propriedades de classe Babel ES7.

Você terá muitos elementos DIV sem className s. Veja o último ponto de https://github.com/jashkenas/backbone/issues/3560#issuecomment -90739676, https://github.com/jashkenas/backbone/issues/3560#issuecomment -91601515 e https: // github .com / jashkenas / backbone / issues / 3560 # issuecomment -98827719.

Entendo. Nesse caso, sugiro ir com a opção "Passar todas as propriedades como opções padrão para o construtor da superclasse", ou a última linha sobre a criação de um método de "propriedades" (que não toque no construtor).

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

Fiz algo semelhante no Toolkit, que pode ser visto aqui: https://github.com/titon/toolkit/issues/107

Oi.

Se bem entendi a discussão aqui - os desenvolvedores do Backbone estão discutindo soluções alternativas e melhores práticas, mas não têm nenhuma intenção de realmente fazer alterações no núcleo do BB para lidar com esse problema? (Não estou sugerindo que devam, nem tenho ideia de quais poderiam ser essas mudanças). Em outras palavras, a sugestão de usar todas as propriedades como funções ou getters é a palavra final sobre o assunto? Obrigado.

@gotofritz Estamos discutindo soluções alternativas porque a solução do ES6 de forçar todas as propriedades a viver em instâncias não é escalável. O sistema de classes do Backbone está fazendo a coisa certa aqui.

alguma discussão sobre a adição de propriedades de protótipo estáticas às classes ES7, mas até agora nada de concreto. Nesse ínterim, eu diria para ficar com extend do Backbone.

Obrigado. Vou tentar aulas ES6 por mais um pouco ... :-)

Para o benefício de qualquer pessoa que encontrar isso, na prática, acho melhor "Passar todas as propriedades como opções padrão para o construtor da superclasse" - por exemplo, nosso aplicativo tem rotas dinâmicas (localizadas) que precisam ser passadas no momento da instanciação, e ter um método routes () simplesmente não funciona. Considerando que o seguinte faz

class Router extends Backbone.Router {

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

Acabei de dar uma olhada nisso e acho que as duas soluções alternativas não funcionam para a propriedade idAttribute um modelo. Um método não funcionará porque o Backbone usa model.idAttribute para acessar a propriedade; E o construtor de modelo não parece suportar a adição de propriedades como opções.

Acho que as duas soluções alternativas não funcionam para a propriedade idAttribute

Excelente captura, vou trabalhar em um PR abordando isso. Nesse ínterim, você pode usar a notação getter para fornecer idAttribute (e cidPrefix ):

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

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

Um método não funcionará porque o Backbone usa model.idAttribute para acessar a propriedade

get idAttribute() { return '_id'; } é um método getter, que é acessado como uma propriedade normal. this.idAttribute === '_id'; .

Isso está começando a parecer que uma grande reescrita é necessária. Backbone v2, talvez?

Isso está começando a parecer que uma grande reescrita é necessária. Backbone v2, talvez?

De forma alguma, já oferecemos suporte à subclasse ES6 (com exceção de Modelos ). Acho que seria interessante se alguém explorasse a sugestão de propriedade estática de @ akre54 , mas mesmo isso não é necessário com as duas soluções do post original.

@jridgewell , muito obrigado pela solução rápida!

Decoradores foram mencionados acima neste tópico (especificamente a proposta de Yehuda Katz ), e não foi resolvido se isso resolveria o problema.

Eu estava apenas brincando com eles conforme a proposta, e você pode escrever um decorador assim:

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

e então o exemplo que estamos usando pode ser escrito assim

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

Isso parece funcionar muito bem para mim. O decorador é aplicado à classe antes de o construtor da classe ser executado. Esta é apenas uma versão declarativa de dizer

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

Na verdade, eu não testei, mas você provavelmente poderia fazer com que toda a função de extensão do backbone fosse o decorador se quisesse adereços estáticos e de protótipo.

Infelizmente esta é apenas uma proposta por enquanto, mas Babel a apóia por trás de uma bandeira experimental, então se as pessoas estão se sentindo aventureiras, é uma solução possível aqui.

@benmccormick , a técnica do decorador funciona bem para mim. Além de ser apenas uma proposta por enquanto, há outras preocupações em seguir essa abordagem?

@andrewrota Estou literalmente escrevendo um post no blog acompanhando essas coisas agora (estava lendo este tópico quando você comentou). Esse é um grande "diferente de", mas pessoalmente não vejo nenhum. Na verdade, acho que podemos fazer melhor do que o que descrevi acima e criar algumas interfaces novas e legais para o Backbone com decoradores.

Veja esta essência de @StevenLangbroek que me fez pensar sobre isso originalmente: https://gist.github.com/StevenLangbroek/6bd28d8201839434b843

Aqui está uma prévia da postagem de acompanhamento que estou publicando: http://benmccormick.org/2015/07/06/backbone-and-es6-classes-revisited/ Atualizado com link permanente agora

Em algum momento, ele será movido para um url permanente no início desta semana. Mas o resumo básico deste tópico e o que aprendi é:

Existem 3 abordagens para fazer as propriedades do Backbone funcionarem com a especificação de classes ES6 atual (as 2 primeiras precisam de # 3684 para serem consideradas totalmente suportadas):

  1. Passe todas as propriedades para o super no construtor
  2. Trate todas as propriedades como métodos
  3. Adicionar propriedades diretamente ao protótipo depois que uma classe foi declarada

Ainda vejo tudo isso como uma limitação da expressividade de uma forma ou de outra. Mas acho que o problema será mais ou menos resolvido se os decoradores se tornarem uma especificação oficial. Com decoradores, existem mais 2 opções.

  1. Adicione um decorador de adereços que pega os adereços no topo da classe e os adiciona ao protótipo
  2. Crie vários decoradores de propósito especial que permitem uma interface mais expressiva / granular.

Não acho que nenhuma dessas soluções requeira quaisquer modificações adicionais no Backbone além do # 3684, mas haveria um papel interessante para uma biblioteca de decoradores de backbone se / quando os decoradores se tornassem padronizados.

Adoraria qualquer feedback sobre o post antes de publicá-lo na segunda / terça-feira.

@benmccormick Achei que os decoradores são avaliados antes que qualquer construção aconteça, obrigado pela correção. Vou atualizar a essência um pouco. também: um milhão de agradecimentos pela menção no post do blog :) ping-me no twitter quando publicá-lo? : +1:

Poderíamos usar a mesma sintaxe para modelEvents e collectionEvents em Marionette, mas não para gatilhos. Eles poderiam ser expostos por meio de um decorador de classe (como tagName e template em sua postagem no blog), mas eu estava pensando: não podemos usar propriedades estáticas para isso? Ou isso não funciona no Backbone?

Eu entendo que os decoradores ainda estão no estágio 0, mas acho que eles serão uma grande atualização na forma como escrevemos aplicativos de Backbone, especialmente os decoradores de métodos em vez de hash de eventos, é o tipo de estilo de programação que me faz preferir engolir em vez de grunhir também.

@StevenLangbroek veja acima a discussão sobre propriedades estáticas.

A sintaxe conforme especificada atualmente cria uma propriedade local em cada instância em vez de adicionar ao protótipo. Essas propriedades são adicionadas depois que o super construtor é executado.

@benmccormick , a postagem parece boa e acho que explica bem as

O decorador de @benmccormick deve chamar _#extend com o construtor e não o protótipo e então o método _.instOrStaticVar @ akre54 é usado no lugar de _#result ? Sei que seria uma mudança significativa, mas parece mais limpo assim, IMHO. Como @ akre54 apontou, as propriedades definidas dessa forma são strings e objetos compartilhados por

Vou mais longe e faço para trabalhar as propriedades da classe da maneira que precisamos. As propriedades da classe também podem ser anotadas e podemos criar um decorador especial, que anexa a propriedade decorada ao protótipo.

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

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

Veja Babel REPL com exemplo. Baseia-se em coisas experimentais, mas funciona.

@ just-boris, conforme discutido nos comentários do meu blog, o comportamento que você está vendo é um detalhe de implementação da manipulação de Babel das propriedades da classe e especificações dos decoradores. Seu comportamento não está definido em nenhuma proposta no momento. Se você quiser fazer as coisas dessa maneira, deverá criar problemas aqui e / ou aqui para tornar os decoradores nas propriedades de classe um comportamento padronizado. Caso contrário, o que você está fazendo pode (e provavelmente irá) falhar a qualquer momento.

@benmccormick wycats / javascript-decorators já tem uma definição extra com relação aos inicializadores de propriedade .

A principal preocupação é que os inicializadores de propriedade são um descritor comum, assim como os métodos de classe, de forma que os decoradores também possam envolvê-los. Não vejo motivos para me preocupar, embora as especificações dessa seção permaneçam inalteradas

Ah muito legal, eu não tinha visto isso. Obrigado por apontar isso.

Na segunda-feira, 21 de setembro de 2015 às 11h29, Boris Serdiuk [email protected]
escreveu:

@benmccormick https://github.com/benmccormick
https://github.com/wycats/javascript-decorators já tem extras
definição sobre inicializadores de propriedade
https://github.com/wycats/javascript-decorators/blob/master/INITIALIZER_INTEROP.md
.

A principal preocupação é que os inicializadores de propriedade são um descritor comum,
bem como métodos de classe, para que os decoradores também possam envolvê-los. Não vejo
motivos para se preocupar, embora as especificações dessa seção permaneçam inalteradas

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/jashkenas/backbone/issues/3560#issuecomment -142015454
.

Só queria saber os prós / contras de usar https://github.com/typhonjs/backbone-es6 versus a técnica do método sugerida por @benmccormick.

A propósito , obrigado

Além da proposta (# 121) pull-request anexando aqui properties método em ação https://github.com/dsheiko/backbone-abstract/tree/master/demo-es6/src/Js
Como @ akre54 mencionou, Justin já propôs uma solução semelhante (método preInitialize ). Embora já esteja usando no meu branch, ele realmente resolve o problema para mim. Pareceu ser útil também no TypeScript, apesar de não proibir propriedades de classes declarativas.

PS preInitialize soa mais geral e, portanto, melhor neste contexto. Embora seja mais parecido com preConstruct se chamarmos o método antes de todas as tarefas do construtor

Realmente gostaria de ver uma nova proposta de propriedades de classe que as definisse no protótipo. Parece que muitos envolvidos com a proposta estão preocupados com as implicações, mas acho incrivelmente inconsistente que os métodos de classe sejam diretamente anexados ao protótipo, enquanto a proposta de jeffmo os coloca no construtor.

Se eles tivessem anexado propriedades diretamente ao protótipo, você seria capaz de migrar praticamente qualquer código React / Backbone para classes ES2015.

incrível postagem do blog @benmccormick !! vou usar esses decoradores no meu projeto

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

Ele roda normalmente em qualquer ambiente que suporte classes nativamente, transpila bem e parece _muito_ melhor do que defini-los no construtor ou após a declaração. Com propostas para decoradores e propriedades de classe chegando para ES2016 / ES2017, isso pode ser mais um exercício acadêmico do que uma solução de longo prazo para o Backbone, mas algo como isso é definitivamente uma opção viável se 2-3 anos for muito longo de um esperar.

Bem, o fato é que Propriedades de classe ainda está no estágio 1 no sistema de estágio de proposta Ecmascript. Não tenho ideia do porquê, já que parece um gimme em termos de "o que o usuário obtém". Claro, não tenho ideia de que tipo de coisas ele pode quebrar nos bastidores, tanto sintaticamente quanto em termos de implementações de referência.

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

Lendo isso, acho https://github.com/epicmiller/es2015-default-class-properties uma boa abordagem. Ao tentar percebi que o Backbone tinha suporte embutido para isso. Por exemplo:

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

O código acima definirá o MyModel.prototype.idAttribute corretamente. Observe, para TypeScript, o arquivo de declaração precisa ser ligeiramente ajustado para retornar uma interface de função do construtor, mas isso é um detalhe irrelevante para usuários ES6 ...

@ t-beckmann é uma solução bastante boa - parece legível e requer alterações mínimas. Obrigado!

Sei que este tópico está acontecendo há 2 anos, mas ainda é um dos principais (e únicos) resultados ao pesquisar classes de Backbone e ES6, e pensei em compartilhar uma solução potencial usando propriedades de classe mencionadas várias vezes aqui .

Agora que as propriedades da classe estão no Estágio 2 e amplamente disponíveis com a predefinição babel, pensei em dar uma outra olhada. Conforme declarado, o problema com propriedades de instância / membro é que elas não são aplicadas ao protótipo até _after_ constructor() , mas muitas das propriedades que precisam ser definidas são usadas no construtor. As propriedades estáticas são aplicadas imediatamente, mas (por design) não são copiadas para instâncias da classe.

O shim a seguir copia as propriedades estáticas do construtor para a instância antes de executar o construtor (criando efetivamente um novo construtor, aplicando as propriedades e, em seguida, executando o construtor original). Embora seja definitivamente um hack, estou muito satisfeito com o resultado:

O calço:

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

E então em uso:

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

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

Só queria deixá-lo aqui para o caso de ajudar mais alguém, ou se alguém tiver alguma ideia a respeito. Obrigado!

Desculpe obrigatória por reviver um problema antigo.

Seria possível ou vale a pena escrever um plugin babel que transforma uma declaração de classe ES6 para usar Backbone. *. Extend ({...})?

@enzious definitivamente parece possível. Se vale a pena, depende de você :)

A solução de @t-beckmann parece a mais direta. devemos integrar isso no próprio backbone?

Para mim, parece que não está certo, não seria mais adequado ter um método que define o idAttribute?

Além disso, seria incrível se houvesse suporte do Promise. que é uma abordagem mais nativa do que usar jquery Deferred, que eu pessoalmente adoraria ver obsoleto no Backbone.

A história aqui ainda é muito obscura para os aplicativos de Backbone legados renovados para utilizar ferramentas modernas e recursos de linguagem. É especialmente decepcionante ver coisas como Symbol.iterator implementadas e não disponíveis em uma versão de produção.

Para aqueles que ainda procuram respostas mais claras para essa pergunta, estou adicionando o TypeScript a um aplicativo de backbone e achei a solução deste comentário muito útil.

Até agora está funcionando bem, com a desvantagem de ter que anotar explicitamente as propriedades passadas pelo decorador em vez de fazer uma inferência mais agradável.

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

Exemplo de anotação de propriedade explícita:

image

@raffomania , @jridgewell & Co., pelo que vale a pena, minha equipe contornou esse problema adicionando idAttribute ao protótipo fora da classe.

class Example extends ParentExample {
// Métodos de classe etc aqui
}

x.Exemplo = Exemplo;

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

@kamsci eu fiz o mesmo neste branch onde converti Backbone para classes ES6

O backbone usa a _configuração_ ao ponto dos objetos de configuração serem _declarativos_. Isso é bom, mas nunca vai jogar bem com hereditariedade. (Clone a classe e configure-a. Isso não é herança.)

Se vamos escrever um novo código usando backbone, não há problema em pensar de forma diferente. Cortar e colar o código ES5 e, em seguida, fazer com que pareça que o ES6 não funciona. E daí?

Não tenho nenhum problema em passar tudo por um objeto de configuração. Como expor o conteúdo dessa configuração, ou torná-lo mais fácil de ler / trabalhar, é um problema para resolver, não para chorar.

Ninguém deseja executar um construtor duas vezes. Isso é bobagem. Mas, o padrão de

Foo = BackboneThing.extend ({LONG DECLARATIVE OBJECT LITERAL}) também é feio, amante da mãe. Vocês todos estão fazendo isso há tanto tempo que não veem como é feio.

Para sua informação: Eu tenho um grande projeto de marionete e queria usar a sintaxe ES6. Eu criei um transformador jscodeshift que traduz declarações de extensões de Backbone em classes ES6. Faz muitas suposições simplificadoras, mas ainda pode ser útil para alguns de vocês, mesmo que seja apenas um ponto de partida. Ele segue a sintaxe proposta por @ t-beckmann quando tive problemas com decoradores.
https://gist.github.com/maparent/83dfd65a37aaaabc4072b30b67d5a05d

Para mim, parece um equívoco estranho neste tópico. 'propriedades estáticas' para ES6 são propriedades no construtor que existem na classe sem instanciação (Class.extend por exemplo). Neste thread, 'propriedades estáticas' parecem referir-se a atributos nomeados no protótipo com um valor 'estático' (não getters ou funções). Eu entendi direito?

Para propriedades de protótipo com um valor estático, declarar os valores de pré-inicialização do Backbone como valores de retorno de função é uma transição bastante direta e funciona bem como _.result tem o desempenho esperado para padrões, className, id etc. Outras propriedades de instância parecem estar bem declaradas em a parte superior da função de inicialização normalmente. Este problema parece surgir apenas porque nas classes ES6 você não pode definir propriedades de protótipo com um valor estático no momento, apenas getters, setters e funções.

De qualquer forma, as propriedades estáticas do construtor / classe (Class.extend) não são herdadas no backbone como são no ES6. O backbone copia as propriedades estáticas da classe para a nova classe / construtor sempre que executa a função de extensão, em vez de ter essas propriedades herdadas como o ES6 faz. Eu fiz um pr para corrigir isso aqui https://github.com/jashkenas/backbone/pull/4235

Gostaria de receber alguns comentários / feedback, não tenho certeza se vai quebrar alguma coisa, eu testei um pouco e parece funcionar bem. As classes de backbone herdam Class.extend posteriormente, em vez de copiar uma referência para cada novo construtor.

Esta página foi útil?
0 / 5 - 0 avaliações