[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;
    }
})



답변

최근에 이에 대해 블로그를 작성했으며 이러한 시나리오를 처리하기 위해 앱에서 수행하는 몇 가지 작업을 보여주었습니다.

http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/


답변

단일 페이지 앱이 점점 커짐에 따라 사용하지 않는 라이브 뷰를 메모리에 유지하여 재사용 할 수 있도록 유지하기가 어려워 지므로 항상 뷰를 파괴하고 생성합니다.

다음은 메모리 누수를 방지하기 위해 뷰를 정리하는 데 사용하는 단순화 된 버전의 기술입니다.

먼저 모든 뷰가 상속하는 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);
       }
  });