[javascript] Backbone.js : 뷰를 다시 채우거나 다시 만드시겠습니까?
내 웹 응용 프로그램에서 왼쪽 테이블에 사용자 목록이 있고 오른쪽에 사용자 세부 정보 창이 있습니다. 관리자가 테이블에서 사용자를 클릭하면 세부 정보가 오른쪽에 표시되어야합니다.
왼쪽에는 UserListView와 UserRowView가 있고 오른쪽에는 UserDetailView가 있습니다. 일이 잘 작동하지만 이상한 행동이 있습니다. 왼쪽에서 일부 사용자를 클릭 한 다음 그 중 하나에서 삭제를 클릭하면 표시된 모든 사용자에 대해 연속적인 자바 스크립트 확인 상자가 나타납니다.
이전에 표시된 모든보기의 이벤트 바인딩이 제거되지 않은 것 같습니다. 이는 정상으로 보입니다. UserRowView에서 매번 새 UserDetailView를 수행하면 안됩니까? 뷰를 유지하고 참조 모델을 변경해야합니까? 새보기를 만들기 전에 현재보기를 추적하고 제거해야합니까? 나는 일종의 길을 잃었고 어떤 아이디어라도 환영받을 것입니다. 감사합니다 !
다음은 왼쪽보기 (행 표시, 클릭 이벤트, 오른쪽보기 생성)의 코드입니다.
window.UserRowView = Backbone.View.extend({
tagName : "tr",
events : {
"click" : "click",
},
render : function() {
$(this.el).html(ich.bbViewUserTr(this.model.toJSON()));
return this;
},
click : function() {
var view = new UserDetailView({model:this.model})
view.render()
}
})
오른쪽보기 코드 (삭제 버튼)
window.UserDetailView = Backbone.View.extend({
el : $("#bbBoxUserDetail"),
events : {
"click .delete" : "deleteUser"
},
initialize : function() {
this.model.bind('destroy', function(){this.el.hide()}, this);
},
render : function() {
this.el.html(ich.bbViewUserDetail(this.model.toJSON()));
this.el.show();
},
deleteUser : function() {
if (confirm("Really delete user " + this.model.get("login") + "?"))
this.model.destroy();
return false;
}
})
답변
최근에 이에 대해 블로그를 작성했으며 이러한 시나리오를 처리하기 위해 앱에서 수행하는 몇 가지 작업을 보여주었습니다.
답변
단일 페이지 앱이 점점 커짐에 따라 사용하지 않는 라이브 뷰를 메모리에 유지하여 재사용 할 수 있도록 유지하기가 어려워 지므로 항상 뷰를 파괴하고 생성합니다.
다음은 메모리 누수를 방지하기 위해 뷰를 정리하는 데 사용하는 단순화 된 버전의 기술입니다.
먼저 모든 뷰가 상속하는 BaseView를 만듭니다. 기본 아이디어는 내 뷰가 구독하는 모든 이벤트에 대한 참조를 유지하므로 뷰를 삭제할 때 모든 바인딩이 자동으로 바인딩 해제됩니다. 다음은 내 BaseView 구현의 예입니다.
var BaseView = function (options) {
this.bindings = [];
Backbone.View.apply(this, [options]);
};
_.extend(BaseView.prototype, Backbone.View.prototype, {
bindTo: function (model, ev, callback) {
model.bind(ev, callback, this);
this.bindings.push({ model: model, ev: ev, callback: callback });
},
unbindFromAll: function () {
_.each(this.bindings, function (binding) {
binding.model.unbind(binding.ev, binding.callback);
});
this.bindings = [];
},
dispose: function () {
this.unbindFromAll(); // Will unbind all events this view has bound to
this.unbind(); // This will unbind all listeners to events from
// this view. This is probably not necessary
// because this view will be garbage collected.
this.remove(); // Uses the default Backbone.View.remove() method which
// removes this.el from the DOM and removes DOM events.
}
});
BaseView.extend = Backbone.View.extend;
뷰가 모델 또는 컬렉션의 이벤트에 바인딩해야 할 때마다 bindTo 메서드를 사용합니다. 예를 들면 :
var SampleView = BaseView.extend({
initialize: function(){
this.bindTo(this.model, 'change', this.render);
this.bindTo(this.collection, 'reset', this.doSomething);
}
});
뷰를 제거 할 때마다 모든 것을 자동으로 정리하는 dispose 메서드를 호출합니다.
var sampleView = new SampleView({model: some_model, collection: some_collection});
sampleView.dispose();
저는이 기술을 “Backbone.js on Rails”ebook을 작성하는 사람들과 공유했으며 이것이 그들이 책에 채택한 기술이라고 믿습니다.
업데이트 : 2014-03-24
Backone 0.9.9부터는 위에 표시된 것과 동일한 bindTo 및 unbindFromAll 기술을 사용하여 listenTo 및 stopListening이 이벤트에 추가되었습니다. 또한 View.remove는 stopListening을 자동으로 호출하므로 바인딩 및 바인딩 해제가 다음과 같이 쉽습니다.
var SampleView = BaseView.extend({
initialize: function(){
this.listenTo(this.model, 'change', this.render);
}
});
var sampleView = new SampleView({model: some_model});
sampleView.remove();
답변
이것은 일반적인 조건입니다. 매번 새보기를 만들면 모든 이전보기가 모든 이벤트에 바인딩됩니다. 한 가지 할 수있는 일은 뷰에 다음과 같은 함수를 만드는 것입니다 detatch
.
detatch: function() {
$(this.el).unbind();
this.model.unbind();
그런 다음 새보기를 만들기 전에 detatch
이전보기 를 호출해야합니다 .
물론 언급했듯이 항상 하나의 “세부 사항”보기를 만들고 변경할 수 없습니다. 뷰에서 모델의 “변경”이벤트에 바인딩하여 자신을 다시 렌더링 할 수 있습니다. 이니셜 라이저에 다음을 추가하십시오.
this.model.bind('change', this.render)
이렇게하면 모델이 변경 될 때마다 세부 정보 창이 다시 렌더링됩니다. “change : propName”이라는 단일 속성을 살펴보면 더 세분화 할 수 있습니다.
물론 이렇게하려면 항목보기가 참조하는 공통 모델과 상위 수준 목록보기 및 세부 정보보기가 필요합니다.
도움이 되었기를 바랍니다!
답변
이벤트 바인딩을 여러 번 수정하려면
$("#my_app_container").unbind()
//Instantiate your views here
경로에서 새 뷰를 인스턴스화하기 전에 위의 줄을 사용하여 좀비 뷰와 관련된 문제를 해결했습니다.
답변
대부분의 사람들이 Backbone으로 시작하면 코드에서와 같이 뷰를 만들 것입니다.
var view = new UserDetailView({model:this.model});
이 코드는 기존 뷰를 정리하지 않고 계속해서 새로운 뷰를 생성 할 수 있기 때문에 좀비 뷰를 생성합니다. 그러나 앱의 모든 백본 뷰에 대해 view.dispose ()를 호출하는 것은 편리하지 않습니다 (특히 for 루프에서 뷰를 생성하는 경우).
정리 코드를 넣는 가장 좋은 타이밍은 새 뷰를 만들기 전이라고 생각합니다. 내 해결책은이 정리를 수행하는 도우미를 만드는 것입니다.
window.VM = window.VM || {};
VM.views = VM.views || {};
VM.createView = function(name, callback) {
if (typeof VM.views[name] !== 'undefined') {
// Cleanup view
// Remove all of the view's delegated events
VM.views[name].undelegateEvents();
// Remove view from the DOM
VM.views[name].remove();
// Removes all callbacks on view
VM.views[name].off();
if (typeof VM.views[name].close === 'function') {
VM.views[name].close();
}
}
VM.views[name] = callback();
return VM.views[name];
}
VM.reuseView = function(name, callback) {
if (typeof VM.views[name] !== 'undefined') {
return VM.views[name];
}
VM.views[name] = callback();
return VM.views[name];
}
VM을 사용하여 뷰를 생성하면 view.dispose ()를 호출하지 않고도 기존 뷰를 정리하는 데 도움이됩니다. 다음에서 코드를 약간 수정할 수 있습니다.
var view = new UserDetailView({model:this.model});
…에
var view = VM.createView("unique_view_name", function() {
return new UserDetailView({model:this.model});
});
따라서 뷰를 지속적으로 생성하는 대신 재사용하려는 경우, 뷰가 깨끗한 한 걱정할 필요가 없습니다. createView를 재사용 뷰로 변경하십시오.
var view = VM.reuseView("unique_view_name", function() {
return new UserDetailView({model:this.model});
});
자세한 코드 및 속성은 https://github.com/thomasdao/Backbone-View-Manager에 게시됩니다.
답변
한 가지 대안은 일련의 새 뷰를 만든 다음 해당 뷰의 바인딩을 해제하는 대신 바인딩하는 것입니다. 다음과 같은 작업을 수행합니다.
window.User = Backbone.Model.extend({
});
window.MyViewModel = Backbone.Model.extend({
});
window.myView = Backbone.View.extend({
initialize: function(){
this.model.on('change', this.alert, this);
},
alert: function(){
alert("changed");
}
});
myView의 모델을 myViewModel로 설정하면 사용자 모델로 설정됩니다. 이런 식으로 myViewModel을 다른 사용자로 설정하면 (즉, 속성 변경) 새 속성을 사용하여 뷰에서 렌더링 함수를 트리거 할 수 있습니다.
한 가지 문제는 이로 인해 원본 모델과의 연결이 끊어진다는 것입니다. 컬렉션 개체를 사용하거나 사용자 모델을 뷰 모델의 속성으로 설정하여이 문제를 해결할 수 있습니다. 그러면 뷰에서 myview.model.get ( “model”)으로 액세스 할 수 있습니다.
답변
이 방법을 사용하여 메모리에서 자식 뷰와 현재 뷰를 지 웁니다.
//FIRST EXTEND THE BACKBONE VIEW....
//Extending the backbone view...
Backbone.View.prototype.destroy_view = function()
{
//for doing something before closing.....
if (this.beforeClose) {
this.beforeClose();
}
//For destroying the related child views...
if (this.destroyChild)
{
this.destroyChild();
}
this.undelegateEvents();
$(this.el).removeData().unbind();
//Remove view from DOM
this.remove();
Backbone.View.prototype.remove.call(this);
}
//Function for destroying the child views...
Backbone.View.prototype.destroyChild = function(){
console.info("Closing the child views...");
//Remember to push the child views of a parent view using this.childViews
if(this.childViews){
var len = this.childViews.length;
for(var i=0; i<len; i++){
this.childViews[i].destroy_view();
}
}//End of if statement
} //End of destroyChild function
//Now extending the Router ..
var Test_Routers = Backbone.Router.extend({
//Always call this function before calling a route call function...
closePreviousViews: function() {
console.log("Closing the pervious in memory views...");
if (this.currentView)
this.currentView.destroy_view();
},
routes:{
"test" : "testRoute"
},
testRoute: function(){
//Always call this method before calling the route..
this.closePreviousViews();
.....
}
//Now calling the views...
$(document).ready(function(e) {
var Router = new Test_Routers();
Backbone.history.start({root: "/"});
});
//Now showing how to push child views in parent views and setting of current views...
var Test_View = Backbone.View.extend({
initialize:function(){
//Now setting the current view..
Router.currentView = this;
//If your views contains child views then first initialize...
this.childViews = [];
//Now push any child views you create in this parent view.
//It will automatically get deleted
//this.childViews.push(childView);
}
});