注意:我知道其他库(例如 Marionette)可以大大简化基于 View 的问题。但是,让我们假设这不是一个选项。
假设我们有一个给定“记录”(即模型)的父 View 。该父 View 有两个 subview ,一个用于显示记录的属性,一个用于编辑它们(假设在这种情况下不适合就地编辑)。到目前为止,每当我需要删除/显示 subview 时,我都会调用 remove。在即将离任的观点和new在传入 View 中,所以我每次都在销毁/创建它们。这很简单,易于编码/管理。
然而,弄清楚是否有任何可行的替代方法(看似默认的)删除/创建方法似乎很重要 - 特别是因为它之前被问过几次,但从未完全回答(例如,Swap out views with Backbone?)
所以我一直在想办法让两个 subview 共享父 View 中的一个元素,从而避免我不得不 remove和 new他们每次。当一个需要激活时,它被渲染而另一个被“沉默”(即不响应事件)。所以它们会不断地换出,直到父 View 被移除,然后它们一起被移除。
可以在这里找到我的想法:http://jsfiddle.net/nLcan/
注意:交换在 RecordView.editRecord 中执行和 RecordView.onRecordCancel .
虽然这看起来工作得很好,但我有一些担忧:
(1) 即使事件绑定(bind)在“非事件” View 上被静音,将两个 View 设置为同一元素是否会出现问题?只要仅呈现“事件” View ,这似乎就不是问题。
(2) 当两个 subview 都有remove调用(即 Backbone.View.remove )它调用 this.$el.remove .现在,当第一个 subview 被移除时,这实际上将移除它们共享的 DOM 元素。因此,当remove在第二个 subview 上调用没有要删除的 DOM 元素,我想知道这是否会使该 subview 难以自行清理 - 特别是如果它本身创建了许多 DOM 元素,然后在第一个 subview 已呈现....或者如果涉及 subview ...似乎可能存在关于此处内存泄漏的潜在问题。
抱歉,据我所知,这有点令人费解。我在我的知识库的边界上是正确的,所以我不完全理解这里涉及的所有潜在问题(因此是问题)。但我确实希望有人处理过类似的问题,并且可以就这一切提出明智的意见。
无论如何,这是完整的(简化示例)代码:
// parent view for displaying/editing a record. creates its own DOM element.
var RecordView = Backbone.View.extend({
tagName : "div",
className : "record",
events : {
"click button[name=edit]" : "editRecord",
"click button[name=remove]" : "removeRecord",
},
initialize : function(settings){
// create the two subviews. one for displaying the field(s) and
// one for editing them. they both listen for our cleanup event
// which causes them to remove themselves. the display view listens
// for an event telling it to update its data.
this.displayView = new RecordDisplayView(settings);
this.displayView.listenTo(this,"cleanup",this.displayView.remove);
this.displayView.listenTo(this,"onSetData",this.displayView.setData);
this.editView = new RecordEditView(settings);
this.editView.listenTo(this,"cleanup",this.editView.remove);
// the editView will tell us when it's finished.
this.listenTo(this.editView,"onRecordSave",this.onRecordSave);
this.listenTo(this.editView,"onRecordCancel",this.onRecordCancel);
this.setData(settings.data,false);
this.isEditing = false;
this.activeView = this.displayView;
// we have two elements within our recordView, one for displaying the
// the header of the record (i.e., info that doesn't change) and
// one for displaying the subView. the subView element will be
// bound to BOTH of our subviews.
this.html = "<div class='header'></div><div class='sub'></div>";
},
render : function(){
// for an explanation of why .empty() is called first, see: https://stackoverflow.com/questions/21031852/backbone-view-delegateevents-not-re-binding-events-to-subview
this.$el.empty().html(this.html);
this.$(".header").empty().html("<p>Record ID: "+this.data.id+"</p><p><button name='edit'>Edit</button><button name='remove'>Remove</button></p>");
this.delegateEvents(); // allows for re-rendering
this.renderSubView();
return this;
},
// the subviews SHARE the same element.
renderSubView : function() {
this.activeView.setElement(this.$(".sub")).render();
},
remove : function() {
this.stopListening(this.displayView);
this.stopListening(this.editView);
this.trigger("cleanup");
this.displayView = null;
this.editView = null;
return Backbone.View.prototype.remove.call(this);
},
// notify will only be false upon construction call
setData : function(data,notify) {
this.data = data;
if ( notify ) {
this.trigger("onSetData",data);
}
},
/* Triggered Events */
editRecord : function(event) {
if ( !this.isEditing ) {
this.isEditing = true;
this.activeView.silence(); // silence the old view (i.e., display)
this.activeView = this.editView;
this.renderSubView();
}
event.preventDefault();
},
removeRecord : function(event) {
this.remove(); // triggers `remove` on both subviews
event.preventDefault();
},
/* Triggered Events from editView */
onRecordSave : function(data) {
this.setData(data,true);
this.onRecordCancel();
},
onRecordCancel : function() {
this.isEditing = false;
this.activeView.silence(); // silence the old view (i.e., edit)
this.activeView = this.displayView;
this.renderSubView();
}
});
// child view of RecordView. displays the attribute. takes over an existing DOM element.
var RecordDisplayView = Backbone.View.extend({
events : {
// if steps are not taken to silence this view, this event will trigger when
// the user clicks 'cancel' on the editView!
"click button[name=cancel]" : "onCancel"
},
initialize : function(settings){
this.setData(settings.data);
},
setData : function(data) {
this.data = data;
},
render : function(){
this.$el.empty().html("<p><strong>Field:</strong> "+this.data.field+"</p>");
return this;
},
remove : function() {
this.trigger("cleanup");
this.data = null;
return Backbone.View.prototype.remove.call(this);
},
// the view is still attached to a particular element in the DOM, however we do not
// want it to respond to any events (i.e., it's sharing an element but that element has
// been rendered to by another view, so we want to make this view invisible for the time
// being).
silence : function() {
this.undelegateEvents();
},
/* Triggered Events */
onCancel : function() {
alert("I'M SPYING ON YOU! USER PRESSED CANCEL BUTTON!");
}
});
// subView of RecordView. displays a form for editing the record's attributes. takes over an existing DOM element.
var RecordEditView = Backbone.View.extend({
events : {
"click button[name=save]" : "saveRecord",
"click button[name=cancel]" : "cancelRecord"
},
initialize : function(settings){
this.data = settings.data;
},
render : function(){
this.html = "<form><textarea name='field' rows='10'>"+this.data.field+"</textarea><p><button name='save'>Save</button><button name='cancel'>Cancel</button></p></form>";
this.$el.empty().html(this.html);
return this;
},
remove : function() {
this.trigger("cleanup");
this.data = null;
return Backbone.View.prototype.remove.call(this);
},
silence : function() {
this.undelegateEvents();
},
/* Triggered Events */
saveRecord : function(event){
this.data.field = this.$("form textarea[name=field]").val();
this.trigger("onRecordSave",this.data);
event.preventDefault();
},
cancelRecord : function(event){
event.preventDefault();
this.trigger("onRecordCancel");
}
});
// presumably this RecordView instance would be in a list of some sort, along with a bunch of other RecordViews.
var v1 = new RecordView({
data : {id:10,field:"Hi there. I'm some text!"}
});
$("#content").empty().html(v1.render().$el);
//$("#content").empty().html(v1.render().$el); (re-rendering would work fine)
最佳答案
好的,这是我的解决方案:
我没有将 backbone 视为“黑盒”,而是查看了 Backbone.View.remove 和 Backbone.View.setElement 的代码。它们都非常简单,因此我们可以轻松地从图片中完全删除 Backbone.View,而只处理 jQuery。一旦你这样做了,单独在 jQuery 中复制这种行为并通过它似乎证明这种方法绝对没有任何问题。
// equivalent of creating a parent view with a subview through backbone, assuming both
// creating new DOM elements
var parentView = $("<div></div>").attr("id","parent").html("<div class='sub'></div>");
// equivalent to assigning two subviews to the same element in a parent view. no
// problems here.
var subView1 = parentView.find(".sub");
var subView2 = parentView.find(".sub");
// they both reference the same element (outside DOM still), so both would have data
// of 'idx' = 2. there are no problems with this.
subView1.data("idx",1);
subView2.data("idx",2);
// add parentView to the DOM, which adds the element that subView1 and 2 reference.
$("#content").append(parentView);
// equivalent to rendering one subview in backbone and using setElement to swap.
// again, no problems with any of this. you can see that the setElement calls
// happening again and again would be redundant.
subView1 = parentView.find(".sub");
var activeSubView = subView1;
activeSubView.html("subView1: " + subView1.data("idx")); // subView1: 2
subView2 = parentView.find(".sub");
activeSubView = subView2;
activeSubView.html("subView2: " + subView2.data("idx")); // subView2: 2
// when you `remove`, all it does is remove the element from the DOM and empty out
// its jQuery data ("idx") and unbind all the events. nothing is "destroyed". you
// still have a reference to it, so it won't be gc'd. the only difference between
// `remove` and `detach` is that `detach` keeps the jQuery data and events. there
// is no need to `remove` the subViews explicitly, as they are children of the
// parent and so when the parent is removed from the DOM, they come with it.
//subView1.remove();
//subView2.remove();
parentView.remove();
// all of the HTML inside the parentView and subView elements still exists. their events are
// gone and their jQuery data is gone.
console.log(subView1.html()); // "subView2: 2"
console.log(parentView.html()); // "<div class="sub">subView2: 2</div>"
console.log(subView1.data("idx")); // undefined
console.log(subView2.data("idx")); // undefined
// this will ensure that they are cleaned up.
parentView = subView1 = subView2 = null;
关于javascript - Backbone .View : Swapping out two child views that share an element in the parent view,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21053919/