Я хотел поделиться парочкой интересных фактов или потенциальных ошибок (в зависимости от того, как вы на это смотреть), которые недавно возникли при программировании на JavaScript с Ember.js.

Первый связан с тем, что JavaScript передает примитивы, такие как логические значения, строки и числа, по значению, а не примитивы, такие как объекты и массивы, по ссылке. Если вы создаете непримитивное свойство в прототипе объекта JavaScript, то по умолчанию все экземпляры этого объекта будут иметь одно и то же значение свойства. Это не относится к Ember, но часто возникает при установке свойства массива или объекта для Ember Object. Если вы добавите свойство массива к прототипу объекта, тогда все экземпляры этого объекта будут использовать один и тот же массив, что, скорее всего, не то, что вам нужно. Например:

// my-component.js
import Ember from 'ember';
export default Ember.Component.extend({
  // All instances will share the same array!
  myArray: [],
  init() {
    this._super(...arguments);
    this.get('myArray').push('hello')
  }
});
// my-component.hbs
{{#each myArray as |str|}}
  {{str}}
{{/each}}
// application.hbs
{{my-component}} {{! output: hello}}
{{my-component}} {{! output: hello hello}}
{{my-component}} {{! output: hello hello hello}}

К моменту вызова 3-го компонента в один и тот же массив были добавлены 3 строки с "hello"! Это связано с тем, что свойству myArray присваивается значение [] ровно один раз, когда файл впервые читается браузером. Вот два способа решить эту проблему:

  1. Лениво вернуть массив в вычисляемом свойстве:
export default Ember.Component.extend({
  myArray: computed(function() { 
    return []; 
  }
});

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

2. Установите свойство в init().

export default Ember.Component.extend({
  myArray: null,
  init() {
    this._super(...arguments);
    this.set('myArray', []);
    this.get('myArray').push('hello');
  }
});

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

Как напомнил мне Кори Форсайт, тот факт, что массивы и объекты передаются по ссылке, также означает, что const в ES6 ведет себя не так, как вы могли подумать. Он создает изменяемую привязку к ссылке, а не к неизменному значению. Например:

const str = "hello";
str = "world"     // Will throw an error
const a = [1,2,3];
a[0] = 5;         // Completely valid, a is now [5, 2, 3]
a.push(4);        // Completely valid, a is now [5, 2, 3, 4]
a = [5, 6, 7];    // will throw an error

Вы можете узнать больше о const в статье Что означает const в ES6? »

Синхронизируйте свойство модели вашего маршрута с контроллером

Я подумал об этом во время фантастического выступления Балинт Эрид об Ember Data на Ember Conf 2017. Бывают законные обстоятельства, когда вы можете захотеть немедленно вернуть что-то из ловушки Route's Model, например, кэшированные данные, а затем асинхронно получить новые данные и заменить модель на контроллере. Однако важно знать, что значение функции modelFor() маршрута сохранит старое значение.

Разработчик, знакомый с Ember, вероятно, предположит, что для маршрута с именем post свойство model на контроллере post и результат вызова this.modelFor('post') с любого другого маршрута всегда будет таким же. Описанный выше метод асинхронной выборки данных может нарушить это предположение, если не будет реализован с осторожностью. Вот пример, который вы также можете попробовать с помощью этого Twiddle:

import Ember from 'ember';
export default Ember.Route.extend({
  model() {
    Ember.run.schedule('afterRender', () => {
      this.controller.set('model', 'world');
      // This will always return 'hello' 
      // even though the controller's model just changed.
      const routeModel = this.modelFor('application');
      this.controller.set('routeModel', routeModel);
    });
    return 'hello';
  }
});

Шаблон будет выглядеть так:

{{model}} {{! "world" the most up-to-date-value}}
{{routeModel}} {{! "hello" the original value}}

Этот пример завершится ошибкой, даже если вы заключите вызов this.modelFor('application') в отдельный цикл выполнения. Упражнение для читателя: попробуйте сами в Twiddle.

Лучшее решение могло бы заключаться в том, чтобы обернуть модель в объект или массив, который может оставаться согласованным:

export default Ember.Route.extend({
  model() {
    Ember.run.schedule('afterRender', () => {
      this.controller.set('model.name', 'world');

      const routeModel = this.modelFor('application');
      this.controller.set('routeModel', routeModel);
    });
    return {
      name: 'hello'
    };
  }
});
// template
{{model.name}} {{! world}}
{{routeModel.name}} {{! world, same as model}}

Другой вариант - вспомнить, что если вы вызываете Route.transitionTo и передаете ему объект в качестве второго аргумента (вместо строки или числа), маршрутизатор установит эту модель на маршруте и контроллере без вызова модели, beforeModel, и хуки afterModel. Это работает, только если ваш маршрут имеет хотя бы один динамический сегмент, то есть хотя бы 1 динамический параметр в URL-адресе, например my-route /: id. Это также требует, чтобы ваша модель была объектом, а не примитивным типом, таким как строка или число. Вот как это выглядит:

export default Ember.Route.extend({
  model() {
    Ember.run.schedule('afterRender', () => {
      // start transition with new name object
      this.transitionTo('application', { name: 'world' });
      // schedule another run loop after the transition is complete
      Ember.run.schedule('afterRender', () => {
        const routeModel = this.modelFor('application');
        this.controller.set('routeModel', routeModel);
      });
    });
    return { name: 'hello' };
  }
});

Все 3 примера есть в Twiddle.

На сегодня все. Надеюсь, вы узнали что-то полезное об Ember. Если у вас есть вопросы или комментарии, дайте мне знать. Если вам нужна помощь в создании приложения Ember.js, обратитесь к 201 Created.