Backbone.js: обновить или воссоздать представление? - PullRequest
82 голосов
/ 27 сентября 2011

В моем веб-приложении у меня есть список пользователей в таблице слева и панель сведений о пользователе справа. Когда администратор щелкает пользователя в таблице, его данные должны отображаться справа.

У меня есть UserListView и UserRowView слева и UserDetailView справа. Вещи вроде работают, но у меня странное поведение. Если я щелкаю некоторых пользователей слева, а затем нажимаю «Удалить» на одном из них, я получаю последовательные окна подтверждения javascript для всех отображенных пользователей.

Похоже, что привязки событий всех ранее отображенных видов не были удалены, что кажется нормальным. Я не должен делать новый UserDetailView каждый раз на UserRowView? Должен ли я сохранить вид и изменить его эталонную модель? Должен ли я отслеживать текущий вид и удалять его перед созданием нового? Я немного потерян, и любая идея будет приветствоваться. Спасибо!

Вот код левого представления (отображение строки, событие щелчка, создание правого представления)

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

Ответы [ 7 ]

136 голосов
/ 30 сентября 2011

Я всегда уничтожаю и создаю представления, потому что по мере того, как мое одностраничное приложение становится все больше и больше, хранение неиспользуемых живых представлений в памяти только для того, чтобы я мог их повторно использовать, становится все труднее поддерживать.

Вот упрощенныйверсия метода, который я использую для очистки моих представлений, чтобы избежать утечек памяти.

Сначала я создаю BaseView, от которого наследуются все мои представления.Основная идея заключается в том, что мой View будет сохранять ссылку на все события, на которые он подписан, так что, когда придет время утилизировать View, все эти привязки будут автоматически отменены.Вот пример реализации моего 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;

Всякий раз, когда необходимо привязать View к событию модели или коллекции, я бы использовал метод 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", и я считаю, что именно эту технику они приняли для этой книги.

Обновление: 2014-03-24

Начиная с версии 0.9.9, к событиям добавлены listenTo и stopListening с использованием тех же методов bindTo и unbindFromAll, которые показаны выше.Кроме того, 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();
27 голосов
/ 27 сентября 2011

Я недавно писал об этом в блоге и показал несколько вещей, которые я делаю в своих приложениях для обработки этих сценариев:

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

8 голосов
/ 27 сентября 2011

Это общее условие. Если вы каждый раз создаете новое представление, все старые представления по-прежнему будут привязаны ко всем событиям. Одна вещь, которую вы можете сделать, это создать функцию в вашем представлении под названием detatch:

detatch: function() {
   $(this.el).unbind();
   this.model.unbind();

Затем, прежде чем создавать новый вид, обязательно вызовите detatch в старом виде.

Конечно, как вы упомянули, вы всегда можете создать один «подробный» вид и никогда не изменять его. Вы можете привязать к событию «изменение» на модели (из вида), чтобы выполнить повторную визуализацию. Добавьте это к вашему инициализатору:

this.model.bind('change', this.render)

Это приведет к повторному рендерингу панели сведений КАЖДЫЙ раз, когда в модель будут внесены изменения. Вы можете получить более высокую степень детализации, наблюдая за одним свойством: "change: propName".

Конечно, для этого требуется общая модель, на которую ссылается представление элемента, а также представление списка более высокого уровня и представление сведений.

Надеюсь, это поможет!

6 голосов
/ 09 декабря 2012

Чтобы исправить привязку событий несколько раз,

$("#my_app_container").unbind()
//Instantiate your views here

Используя приведенную выше строку перед созданием новых видов из маршрута, я решил проблему, связанную с представлениями зомби.

2 голосов
/ 25 мая 2012

Я думаю, что большинство людей, начинающих с 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 на reuseView:

var view = VM.reuseView("unique_view_name", function() {
                return new UserDetailView({model:this.model});
           });

Подробный код и атрибуция размещены на https://github.com/thomasdao/Backbone-View-Manager

0 голосов
/ 05 ноября 2014

Используйте этот метод для очистки дочерних и текущих представлений из памяти.

//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);
       }
  });
0 голосов
/ 01 июня 2012

Одной из альтернатив является привязка, в отличие от создания серии новых представлений, а затем отмены привязки этих представлений.Вы бы сделали это, выполнив что-то вроде:

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 («модель»).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...