Backbone: Backbone и классы ES6

Созданный на 7 апр. 2015  ·  63Комментарии  ·  Источник: jashkenas/backbone

С окончательными изменениями в спецификации класса ES6 (подробности здесь ) больше невозможно использовать классы ES6 с Backbone без значительных компромиссов с точки зрения синтаксиса. Я написал полное описание ситуации здесь (не забудьте щелкнуть комментарии внизу, чтобы получить дополнительный параметр смягчения), но по существу нет способа добавить свойства к экземпляру подкласса до родителей подклассов конструктор запущен.

Итак, это:

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

больше не действует в окончательной спецификации ES6. Вместо этого у вас есть 3 (не очень привлекательных) варианта, если вы хотите попробовать эту работу:

Прикрепите все свойства как функции

Backbone позволяет это, но кажется глупым писать что-то вроде этого:

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

по сравнению с текущим расширением синтаксиса

Дважды запустить конструктор

Я не рассматриваю это как реальный вариант из-за проблем, которые могут вызвать повторную инициализацию с разными идентификаторами и т. Д.

Передайте все свойства как параметры по умолчанию в конструктор суперкласса

Это было предложено комментатором в моем блоге и, вероятно, является наиболее практичным из существующих вариантов. Выглядит это примерно так:

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

Поскольку все эти текущие параметры предполагают явные компромиссы по сравнению с текущим синтаксисом Backbone extends, было бы замечательно, если бы можно было разработать лучшее решение. Я не совсем уверен, как это должно выглядеть, но одна идея, которая пришла мне в голову, когда я писал для своего блога, заключалась в добавлении функции «свойств», которая выводила бы хэш свойств. Затем конструктор может запустить эту функцию и добавить их в экземпляр до другой обработки, выполняемой конструктором.

Самый полезный комментарий

Читая это, я считаю, что https://github.com/epicmiller/es2015-default-class-properties является хорошим подходом. При попытке я понял, что Backbone имеет встроенную поддержку для этого. Например:

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

Приведенный выше код правильно установит MyModel.prototype.idAttribute. Обратите внимание, что для TypeScript файл объявления необходимо немного скорректировать, чтобы он возвращал интерфейс функции конструктора, но эта деталь не имеет отношения к пользователям ES6 ...

Все 63 Комментарий

Да, это определенно облом. Спасибо за беготню.

Я предполагаю, что мораль этой истории в том, что не используйте классы ES6 с Backbone, по крайней мере, до тех пор, пока не появится поддержка статических свойств. Из предложенных вами альтернативных вариантов мое предпочтительное решение - это определение строк / объектов как возвращаемых значений. Ключевая часть дизайна API Backbone заключается в этих общих строках и объектах прототипа, и это приведет к загрязнению API, требуя от разработчиков назначать каждое свойство экземпляру в конструкторе (не говоря уже о том, что это расточает память).

Помимо согласованности, есть ли причина использовать ключевое слово class с Backbone вместо extend ?

Отличный пост в блоге. Мне было интересно, как классы ES6 и Backbone будут играть вместе. Что касается ваших решений:

  1. Присоедините все свойства как функции : я не против этого. Это не так чисто, как установка объекта непосредственно на прототипе, но я видел, как тонна кода ошибалась при изменении объектов прототипа. Этот способ невосприимчив, поэтому я думаю, что ES6 решил не включать свойства классов.
  2. Передайте все свойства как параметры по умолчанию : разве не так вы бы сделали что-то на более классическом языке? Мне кажется, что это еще менее чистое решение, чем приведенное выше.
  3. Дважды запустить конструктор : Ick.

Я предполагаю, что мораль этой истории в том, что не используйте классы ES6 с Backbone, по крайней мере, до тех пор, пока не появится поддержка статических свойств.

Даже свойства класса идут после вызова super() . :расстроенный:

Помимо согласованности, есть ли причина использовать ключевое слово class с Backbone over extend?

Я обратился к этому в своем блоге. Практически? Нет. Теоретически это позволило бы Backbone в долгосрочной перспективе сократить количество кода и дополнительных концепций, но на практике это произойдет как минимум за несколько лет до того, как классы ES6 будут широко поддерживаться во всех соответствующих браузерах без транспиляции, и сокращение кода будет следующим. ничего.

Но не стоит недооценивать аспект согласованности. Если это станет «способом» выполнения объектно-ориентированного программирования на JavaScript (вероятно, учитывая стандартизацию этого из Ember / Angular / React / Typescript / Aurelia и т. Д.), Backbone, не использующий его, будет дополнительной кривой обучения для библиотеки по сравнению с другие варианты. Специально для младших разработчиков. Я не уверен, что это обязательно заслуживает изменения. Но это не только для педантичной постоянства «хобгоблин маленьких умов».

Я согласен с @ akre54 и @jridgewell в том, что

ES7 будет иметь правильные свойства класса, я думаю https://gist.github.com/jeffmo/054df782c05639da2adb

Предложение ES7 - это очень раннее предложение, продвигаемое сообществом. Совершенно не ясно, что он когда-либо станет частью официальной спецификации. Текущие реализации вызывают добавление свойств к экземпляру ПОСЛЕ запуска конструктора, поэтому это не помогает с Backbone. (см. ссылку jridgewell выше или попробуйте сами с Babel 5.0.0)

@jridgewell Я имел в виду эту часть сообщения @benmccormick :

Разработчики React отметили те же проблемы с инициализаторами свойств, с которыми сталкиваются пользователи Backbone. Как часть версии 0.13 React, они поддерживают специальный синтаксис инициализации свойств для классов, который со временем может быть стандартизирован. Больше информации об этом можно найти в

См., Например , js-decorators Straight от wycats или исходное (замененное) предложение классов гармонии .

Я могу предложить использовать геттеры со свойствами класса:

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

В крайнем случае, мы могли бы проверить экземпляры или статические свойства с помощью помощника а-ля _.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;
}

Ага, но:

К сожалению, эта версия определяет свойства класса как экземпляры, создаваемые после запуска конструктора суперкласса, поэтому здесь проблемы Backbone не решаются.

Итак, 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
});

Наш элемент все равно будет построен до того, как мы внесем изменения в установку переменной экземпляра.

См., Например, js-decorators wycats соломенный ...

Вы можете объяснить, как будут применяться декораторы?

Я могу предложить использовать геттеры со свойствами класса:

: +1 :. Я вижу это как ту же лодку, что и все свойства как функции . Не так чисто, как сейчас, но вполне приемлемо и устойчиво к мутациям.

В крайнем случае, мы могли бы проверить экземпляры или статические свойства с помощью помощника a la _.result:

Это может быть интересно ...

Вы могли:

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

@thejameskyle Это параметр « Передать все свойства как параметры по умолчанию для параметра конструктора суперкласса» . : stuck_out_tongue:

Вместо того, чтобы полагаться на super() при настройке класса, вы могли бы просто иметь функцию init() или что-то в этом роде.

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 хм? Это немедленно приведет к ошибке с окончательной спецификацией класса ES6

В производном классе вы должны вызвать super (), прежде чем сможете использовать этот

Даже если это сработает, вы никогда не вызовете конструктор Backbone и не получите его код инициализации.

См. Ссылку из моего первого сообщения: http://www.2ality.com/2015/02/es6-classes-final.html

@milesj : Дело в том, что вы должны вызвать super() перед установкой this.tagName или чего-то подобного. И, поскольку мы обеспечиваем наличие элемента в конструкторе View, мы уже создали элемент до того, как установим this.tagName .

@milesj это все еще запрещено при

@jridgewell Ой, извини, я это пропустил. Это кажется наиболее естественным вариантом. Я поговорил об этом с Jeffmo и Sebmck.

Чтобы дать вам некоторую предысторию, аргументация заключается в том, что для поддержки расширения собственных типов (т.е. массива) this не определяется до тех пор, пока вы не вызовете метод super() . В противном случае вы столкнетесь с проблемой инициализации в DOM (и, предположительно, в других местах).

@jridgewell @thejameskyle Затем просто сначала вызовите super () (обновленный пример). Я действительно не вижу здесь проблемы, поскольку я делал то же самое в своих классах ES6. Просто переместите логику конструктора представлений в метод init() .

Это много очень дорогой код дважды бежать.

@milesj вы читали исходное сообщение в блоге? Запуск super first означает, что свойства не обрабатываются. См. Подробное объяснение здесь: http://benmccormick.org/2015/04/07/es6-classes-and-backbone-js/

Да, я читал это, и мне все еще любопытно, почему это не решение. Все продолжают говорить о необходимости вызова конструктора представлений, но это не обязательно так. Почему что-то вроде следующего не является решением (хотя и немного надуманным)?

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

Я предполагаю из-за обратной совместимости с не-ES6?

Тогда класс по умолчанию View не будет работать, поскольку конструктор никогда не вызывает #setup . И принудительный вызов подкласса чего-либо, кроме super() , будет очень раздражающим.

Это проблема, с которой сталкиваются все классы ES6, а не только Backbone. Я лично решил это, используя спецификацию свойств класса Babel ES7.

@milesj Как указывалось ранее, свойства класса ES7 не решают эту проблему, поскольку они не создаются до конца конструктора.

Я поговорил с jeffmo и sebmck об этом:

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:

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

Но это все еще не решает проблему и приводит к несогласованности:

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

Это проблема, с которой сталкиваются все классы ES6, а не только Backbone.

Хм?

Я лично решил это, используя спецификацию свойств класса Babel ES7.

У вас будет много элементов DIV без className s. См. Последний пункт https://github.com/jashkenas/backbone/issues/3560#issuecomment -90739676, https://github.com/jashkenas/backbone/issues/3560#issuecomment -91601515 и https: // github .com / jashkenas / backbone / issues / 3560 # issuecomment -98827719.

Понятно. В этом случае я бы предложил использовать параметр «Передать все свойства в качестве параметров по умолчанию конструктору суперкласса» или последнюю строку о создании метода «свойств» (который не касается конструктора).

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

Я сделал нечто подобное в Toolkit, который можно увидеть здесь: https://github.com/titon/toolkit/issues/107

Привет.

Если я правильно понимаю обсуждение здесь - разработчики Backbone обсуждают обходные пути и лучшие практики, но не собираются фактически вносить изменения в ядро ​​BB для решения этой проблемы? (Я не предлагаю им, и я бы не имел ни малейшего представления, какими могут быть эти изменения). Другими словами, последнее слово по теме - это предложение использовать все свойства как функции или методы получения? Спасибо.

@gotofritz Мы обсуждаем обходные пути, потому что решение ES6 по принудительному размещению всех свойств в экземплярах не масштабируется. Система классов Backbone делает здесь правильные вещи.

Есть некоторое обсуждение добавления статических свойств прототипа к классам ES7, но пока ничего конкретного. А пока я бы посоветовал придерживаться extend Backbone.

Спасибо. Я попробую классы ES6 еще немного ... :-)

Для удобства всех, кто сталкивается с этим, на практике я считаю, что «Передать все свойства как параметры по умолчанию конструктору суперкласса» лучше - например, в нашем приложении есть динамические (локализованные) маршруты, которые необходимо передать во время создания экземпляра, и наличие метода routes () просто не работает. Принимая во внимание следующее

class Router extends Backbone.Router {

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

Я только что взглянул на это и думаю, что оба обходных пути не работают для свойства idAttribute которое есть у модели. Метод не будет работать, поскольку Backbone использует model.idAttribute для доступа к свойству; И конструктор модели, похоже, не поддерживает добавление свойств в качестве параметров в целом.

Я думаю, что оба обходных пути не работают для свойства idAttribute.

Отличный улов, я поработаю над PR по этому поводу. А пока вы можете использовать нотацию геттера для предоставления пользовательских idAttributecidPrefix ):

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

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

Метод не будет работать, поскольку Backbone использует атрибут model.idAttribute для доступа к свойству.

get idAttribute() { return '_id'; } - это метод получения, доступ к которому осуществляется так же, как и к обычному свойству. this.idAttribute === '_id'; .

Это начинает звучать так, как будто требуется серьезная переработка. Возможно, Backbone v2?

Это начинает звучать так, как будто требуется серьезная переработка. Возможно, Backbone v2?

Вовсе нет, мы уже поддерживаем создание подклассов ES6 (за исключением моделей ). Я думаю, было бы интересно, если бы кто-нибудь исследовал предложение статического свойства @ akre54 , но даже это не обязательно с двумя решениями в исходном сообщении.

@jridgewell , большое спасибо за быстрое решение!

Декораторы были упомянуты выше в этой ветке (в частности , предложение Иегуды Каца ), и не было решено, решит ли это эту проблему.

Я просто играл с ними, как было предложено, и вы можете написать декоратор следующим образом:

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

и тогда пример, который мы использовали, можно записать так

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

Мне кажется, это прекрасно работает. Декоратор применяется к классу до выполнения конструктора класса. Это просто декларативная версия выражения

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

На самом деле, я не тестировал его, но вы, вероятно, могли бы сделать всю функцию расширения магистрали декоратором, если вам нужны как статические, так и прототипные свойства.

К сожалению, на данный момент это всего лишь предложение, но Babel поддерживает его в виде экспериментального флага, поэтому, если люди хотят приключений, это возможное решение.

@benmccormick ,

@andrewrota Я буквально пишу сообщение в блоге,

См. Суть от @StevenLangbroek, которая заставила меня задуматься об этом изначально: https://gist.github.com/StevenLangbroek/6bd28d8201839434b843

Вот предварительный просмотр следующего сообщения, которое я публикую: http://benmccormick.org/2015/07/06/backbone-and-es6-classes-revisited/ Обновлено с постоянной ссылкой.

Когда-нибудь в начале этой недели он перейдет на постоянный URL. Но основное резюме из этой ветки и того, что я узнал, таково:

Есть 3 подхода к тому, чтобы заставить свойства Backbone работать с текущей спецификацией классов ES6 (первые 2 нуждаются в # 3684, чтобы считаться полностью поддерживаемыми):

  1. Передайте все свойства суперу в конструкторе
  2. Рассматривать все свойства как методы
  3. Добавляйте свойства непосредственно в прототип после объявления класса

Я до сих пор считаю все это в той или иной степени ограничивающими выразительность. Но я думаю, что проблема более-менее решится, если декораторы станут официальной спецификацией. С декораторами есть еще 2 варианта.

  1. Добавьте декоратор реквизита, который берет реквизиты наверху класса и добавляет их к прототипу.
  2. Создайте несколько декораторов специального назначения, которые сделают интерфейс более выразительным и детализированным.

Я не думаю, что какое-либо из этих решений требует каких-либо дополнительных модификаций Backbone, кроме # 3684, но будет интересная роль для библиотеки backbone-декоратора, если / когда декораторы станут стандартизированными.

Хотел бы получить отзывы о посте, прежде чем я опубликую его в понедельник / вторник.

@benmccormick Я подумал, что декораторы оцениваются до того, как произойдет какое-либо строительство, спасибо за исправление. Я немного обновлю суть. также: большое спасибо за упоминание в блоге :) ping me on twitter, когда вы публикуете это? : +1:

Мы могли бы использовать один и тот же синтаксис для modelEvents и collectionEvents в Marionette, но не для триггеров. Их можно было бы раскрыть с помощью декоратора класса (например, tagName и template в вашем сообщении в блоге), но я подумал: нельзя ли использовать для этого статические свойства? Или это не работает в Backbone?

Я понимаю, что декораторы все еще находятся на стадии 0, но я думаю, что они станут отличным обновлением в том, как мы пишем Backbone-приложения, особенно декораторы методов поверх хэша событий, это тот стиль программирования, который заставляет меня также предпочитать глоток ворчанию.

@StevenLangbroek см. Выше обсуждение статических свойств.

Синтаксис, заданный в настоящее время, создает локальное свойство для каждого экземпляра, а не добавляет его к прототипу. Эти свойства добавляются после запуска суперконструктора.

@benmccormick , пост выглядит хорошо, и я думаю, что он хорошо объясняет компромиссы с каждым из вариантов. На данный момент мне очень нравится подход к декораторам специального назначения, и это, кажется, лучший подход, предполагающий, что декораторы включены в спецификацию.

Должен ли декоратор @benmccormick вызвать _#extend с конструктором, а не прототипом, а затем использовать метод _.instOrStaticVar @ akre54 вместо _#result ? Я понимаю, что это было бы решающим изменением, но, ИМХО, это кажется более чистым. Подобно тому, как @ akre54 указал, что свойства, определенные таким образом, являются общими для прототипа строками и объектами (т.е. общими для всех экземпляров), поэтому к ним следует обращаться через класс, верно?

Я иду дальше и заставляю работать свойства класса так, как нам нужно. Свойства класса также можно аннотировать, и мы можем создать специальный декоратор, который прикрепляет декорированное свойство к прототипу.

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

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

См. Пример Babel REPL . Он основан на экспериментальных вещах, но работает.

@ just-boris, как обсуждалось в моих комментариях в блоге, поведение, которое вы видите там, представляет собой деталь реализации обработки Babel свойств класса и спецификаций декораторов. Его поведение не определено ни в одном предложении прямо сейчас. Если вы хотите сделать что-то таким образом, вы захотите создать проблемы здесь и / или здесь, чтобы сделать декораторы свойств класса стандартизированным поведением. В противном случае то, что вы делаете, может (и, вероятно, сломается) в любой момент.

@benmccormick wycats / javascript-decorators уже имеет дополнительное определение, касающееся инициализаторов свойств .

Основная проблема в том, что инициализаторы свойств - это обычный дескриптор, а также методы класса, поэтому декораторы также могут их оборачивать. Не вижу причин для беспокойства, пока спецификация в этом разделе осталась неизменной.

Ах, очень круто, я этого не видел. Спасибо что подметил это.

В пн, 21 сентября 2015 г., в 11:29, Борис Сердюк [email protected]
написал:

@benmccormick https://github.com/benmccormick
https://github.com/wycats/javascript-decorators уже имеет дополнительные
определение относительно инициализаторов свойств
https://github.com/wycats/javascript-decorators/blob/master/INITIALIZER_INTEROP.md
.

Основная проблема инициализаторов свойств - это обычный дескриптор,
а также методы класса, поэтому декораторы также могут их оборачивать. Я не вижу
поводы для беспокойства, пока спецификация в этом разделе остается неизменной

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/jashkenas/backbone/issues/3560#issuecomment -142015454
.

Просто хотел узнать плюсы и минусы использования https://github.com/typhonjs/backbone-es6 по сравнению с методом, предложенным @benmccormick.

Кстати, спасибо @benmccormick за отличный пост в блоге!

В дополнение к запросу на внесение (# 121) сюда прилагается пул-реквест properties метод в действии https://github.com/dsheiko/backbone-abstract/tree/master/demo-es6/src/Js
Как упоминалось в @ akre54, Джастин уже предложил аналогичное решение (метод preInitialize ). Хотя я уже использую его в своей ветке, он действительно решает проблему для меня. Оказалось, также полезно в TypeScript, несмотря на то, что они не запрещают декларативные свойства класса.

PS preInitialize звучит более обобщенно и, следовательно, лучше в этом контексте. Хотя это больше похоже на preConstruct если мы вызываем метод до всех заданий конструктора.

Очень хотелось бы, чтобы мы увидели новое предложение свойств класса, которое устанавливает их в прототипе. Кажется, что многие участники этого предложения обеспокоены последствиями, но я считаю невероятно непоследовательным, что методы класса напрямую прикрепляются к прототипу, в то время как предложение Джеффмо помещает их в конструктор.

Если бы они прикрепили свойства непосредственно к прототипу, вы бы смогли перенести практически любой код React / Backbone в классы ES2015.

классный пост в блоге

@benmccormick , я придумал другой способ объявления классов со свойствами по умолчанию, взгляните: https://github.com/epicmiller/es2015-default-class-properties

Он нормально работает в любой среде, которая изначально поддерживает классы, хорошо переносится и выглядит _ намного лучше, чем их определение в конструкторе или после объявления. Поскольку предложения по декораторам и свойствам классов поступают в конвейер для ES2016 / ES2017, это может быть скорее академическим упражнением, чем долгосрочным решением для Backbone, но что-то подобное определенно является жизнеспособным вариантом, если 2-3 года - это слишком долго. ждать.

Дело в том, что Class Properties все еще находится на стадии 1 в системе стадии предложения Ecmascript. Понятия не имею, почему, поскольку это похоже на то, «что получает пользователь». Конечно, я понятия не имею, какие вещи он может сломать под капотом как синтаксически, так и с точки зрения эталонных реализаций.

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

Читая это, я считаю, что https://github.com/epicmiller/es2015-default-class-properties является хорошим подходом. При попытке я понял, что Backbone имеет встроенную поддержку для этого. Например:

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

Приведенный выше код правильно установит MyModel.prototype.idAttribute. Обратите внимание, что для TypeScript файл объявления необходимо немного скорректировать, чтобы он возвращал интерфейс функции конструктора, но эта деталь не имеет отношения к пользователям ES6 ...

@ t-beckmann - отличное решение - выглядит читабельным и требует минимальных изменений. Спасибо!

Я понимаю, что этот поток длится уже 2 года, но он по-прежнему является одним из лучших (и единственных) результатов при поиске классов Backbone и ES6, и я подумал, что поделюсь потенциальным решением, использующим свойства класса, упомянутые здесь несколько раз .

Теперь, когда свойства класса находятся на этапе 2 и широко доступны с предустановкой babel, я подумал, что стоит взглянуть на него еще раз. Как уже говорилось, проблема со свойствами экземпляра / члена заключается в том, что они не применяются к прототипу до _after_ constructor() , но многие из свойств, которые необходимо установить, используются в конструкторе. Статические свойства применяются немедленно, но (по замыслу) не копируются в экземпляры класса.

Следующая оболочка копирует статические свойства из конструктора в экземпляр перед запуском конструктора (фактически создавая новый конструктор, применяя свойства, а затем выполняя исходный конструктор). Хотя это определенно взлом, я очень доволен результатом:

Прокладка:

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

А потом в использовании:

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

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

Просто хотел бросить это здесь, на случай, если это поможет кому-то другому, или у кого-то есть какие-то мысли по этому поводу. Спасибо!

Обязательно извините за реанимацию старого вопроса.

Возможно ли или стоит ли написать плагин babel, который преобразует объявление класса ES6 для использования Backbone. *. Extend ({...})?

@enzious определенно кажется возможным. Стоит ли это решать вам :)

Решение @t-beckmann кажется наиболее простым. должны ли мы интегрировать это в саму магистраль?

На мой взгляд, это выглядит неправильно, не было бы правильнее иметь метод, устанавливающий idAttribute?

Кроме того, было бы замечательно, если бы была поддержка Promise. что является более естественным подходом, чем использование jquery Deferred, которое я лично хотел бы видеть устаревшим в Backbone.

История здесь все еще очень неясна для обновления устаревших приложений Backbone для использования современных инструментов и языковых функций. Особенно разочаровывает то, что такие вещи, как Symbol.iterator, реализованы и недоступны в производственной версии.

Для тех, кто все еще ищет более четкие ответы на этот вопрос, я добавляю TypeScript в базовое приложение и считаю решение из этого комментария наиболее полезным.

Пока что он работает достаточно хорошо, с недостатком явной аннотации свойств, передаваемых через декоратор, вместо более удобного вывода.

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

Пример аннотации явного свойства:

image

@raffomania , @jridgewell & Co., чего бы это ни стоило, моя команда решила эту проблему, добавив idAttribute к прототипу вне класса.

class Example extends ParentExample {
// Методы класса и т. Д. Здесь
}

x.Example = Пример;

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

@kamsci я сделал то же самое в этой ветке, где преобразовал Backbone в классы ES6

Backbone использует _configuration_, чтобы объекты конфигурации были _declarative_. Это хорошо, но с наследованием никогда не будет ничего плохого. (Клонируйте класс, а затем настройте его. Это не наследование.)

Если мы собираемся писать новый код, используя магистраль, можно думать иначе. Вырезать и вставить код ES5, а затем заставить его выглядеть так, как будто ES6, не работает. И что?

У меня вообще нет проблем с прохождением всего через объект конфигурации. Как мы раскрываем содержимое этой конфигурации или упрощаем его чтение / работу - это проблема, которую нужно решить, а не о чем плакать.

Никто не хочет запускать конструктор дважды. Это глупо. Но образец

Foo = BackboneThing.extend ({LONG DECLARATIVE OBJECT LITERAL}) тоже уродлив, любящий мать. Вы все так долго этим занимаетесь, что не замечаете, насколько это уродливо.

К вашему сведению: у меня есть большой проект Marionette, и я хотел использовать синтаксис ES6. Я создал преобразователь jscodeshift, который переводит объявления Backbone extends в классы ES6. Он делает много упрощающих предположений, но все же может быть полезен для некоторых из вас, хотя бы в качестве отправной точки. Он следует синтаксису, предложенному @ t-beckmann, когда я столкнулся с проблемами с декораторами.
https://gist.github.com/maparent/83dfd65a37aaaabc4072b30b67d5a05d

Мне кажется, что в этой теме есть странное неправильное название. «статические свойства» для ES6 - это свойства конструктора, которые существуют в классе без создания экземпляра (например, Class.extend). В этом потоке «статические свойства», по-видимому, относятся к именованным атрибутам в прототипе со «статическим» значением (не получателям или функциям). Я правильно понял?

Для свойств прототипа со статическим значением объявление значений предварительной инициализации Backbone в качестве возвращаемых значений функции является довольно простым переходом и хорошо работает, поскольку _.result выполняет ожидаемые для значений по умолчанию, className, id и т. Д. Другие свойства экземпляра, похоже, хорошо объявлены в в верхней части функции инициализации как обычно. Эта проблема, кажется, возникает только потому, что в классах ES6 вы не можете определять свойства прототипа со статическим значением в настоящее время, только геттеры, сеттеры и функции.

В любом случае статические свойства конструктора / класса (Class.extend) не наследуются в магистрали, как в ES6. Backbone копирует статические свойства класса в новый класс / конструктор каждый раз при выполнении функции расширения, а не при наследовании этих свойств, как это делает ES6. Я сделал PR, чтобы исправить это здесь https://github.com/jashkenas/backbone/pull/4235

Я был бы признателен за некоторые комментарии / отзывы, я не уверен, что это сломает что-нибудь, я довольно много тестировал, и, похоже, он работает хорошо. Базовые классы впоследствии наследуют Class.extend, а не копируют ссылку на каждый новый конструктор.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги