Backbone.js строит интерфейс на основе моделей, коллекций и представлений (Views). В крупных приложениях часто возникает необходимость управлять вложенными представлениями, которые называются субпредставлениями. Они позволяют разделять сложный интерфейс на отдельные, независимые компоненты, повышая читаемость кода и упрощая поддержку.
Субпредставление — это представление, которое существует в контексте другого родительского представления. Оно может иметь собственную модель или коллекцию, методы рендеринга и обработчики событий. Ключевое отличие заключается в том, что родительское представление отвечает за жизненный цикл своих субпредставлений: создание, рендеринг, обновление и удаление.
Жизненный цикл субпредставлений включает несколько этапов:
Существует несколько подходов к управлению субпредставлениями:
var ParentView = Backbone.View.extend({
initialize: function() {
this.childViews = [];
},
render: function() {
var childView = new ChildView({ model: this.model.get('child') });
this.childViews.push(childView);
this.$el.append(childView.render().el);
return this;
},
remove: function() {
_.each(this.childViews, function(view) { view.remove(); });
Backbone.View.prototype.remove.call(this);
}
});
var ParentView = Backbone.View.extend({
initialize: function() {
this.childViews = {};
},
addChild: function(id, model) {
var childView = new ChildView({ model: model });
this.childViews[id] = childView;
this.$el.append(childView.render().el);
},
removeChild: function(id) {
if (this.childViews[id]) {
this.childViews[id].remove();
delete this.childViews[id];
}
},
remove: function() {
_.each(this.childViews, function(view) { view.remove(); });
Backbone.View.prototype.remove.call(this);
}
});
Рендеринг субпредставлений должен учитывать производительность и порядок вставки в DOM. Часто используется подход:
DocumentFragment).render: function() {
var fragment = document.createDocumentFragment();
this.collection.each(function(model) {
var childView = new ChildView({ model: model });
fragment.appendChild(childView.render().el);
this.childViews.push(childView);
}, this);
this.$el.empty().append(fragment);
return this;
}
События субпредставлений могут перехватываться родительским представлением через Backbone.Events или напрямую через слушатели моделей и коллекций. Для удобства часто создаются методы-обертки:
initialize: function() {
this.listenTo(this.model, 'change', this.render);
this.listenToCollection();
},
listenToCollection: function() {
this.listenTo(this.collection, 'add', this.addChildView);
this.listenTo(this.collection, 'remove', this.removeChildView);
},
addChildView: function(model) {
var view = new ChildView({ model: model });
this.childViews.push(view);
this.$el.append(view.render().el);
},
removeChildView: function(model) {
var view = _.findWhere(this.childViews, { model: model });
if (view) {
view.remove();
this.childViews = _.without(this.childViews, view);
}
}
Правильное управление субпредставлениями критично для предотвращения утечек памяти:
remove() у субпредставлений при
удалении родителя.remove: function() {
_.each(this.childViews, function(view) {
view.stopListening();
view.remove();
});
this.childViews = [];
this.stopListening();
Backbone.View.prototype.remove.call(this);
}
listenTo и
stopListening вместо прямого on/off для
управления событиями.Эффективное управление субпредставлениями позволяет строить масштабируемые Backbone-приложения с чистой архитектурой и минимальными проблемами с производительностью и памятью.