Я хотел поделиться парочкой интересных фактов или потенциальных ошибок (в зависимости от того, как вы на это смотреть), которые недавно возникли при программировании на 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
присваивается значение []
ровно один раз, когда файл впервые читается браузером. Вот два способа решить эту проблему:
- Лениво вернуть массив в вычисляемом свойстве:
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.