Backbone.js: повторно заповнити чи відтворити подання?


83

У моїй веб-програмі я маю список користувачів у таблиці ліворуч, а область відомостей про користувача праворуч. Коли адміністратор клацає користувача в таблиці, його деталі повинні відображатися праворуч.

У мене є 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;
    }
})

Відповіді:


28

Нещодавно я писав про це в блозі та показав кілька речей, які я роблю в своїх програмах для обробки таких сценаріїв:

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


1
Чому не просто delete viewв маршрутизаторі?
Трантор Лю

Я підтримав вашу відповідь, але було б дуже корисно, якщо відповідні частини допису в блозі будуть розміщені всередині самої відповіді, оскільки це є метою.
Еміль Бержерон,

136

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

Ось спрощена версія техніки, яку я використовую для очищення своїх подань, щоб уникнути витоків пам’яті.

Спочатку я створюю 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;

Кожного разу, коли подання потрібно прив’язати до події на моделі чи колекції, я б використовував метод 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

Починаючи з Backone 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();

Чи є у вас пропозиції щодо розподілу вкладених подань? Зараз я роблю подібне до bindTo: gist.github.com/1288947, але я думаю, що можна зробити щось краще.
Дмитро Полушкін

Дмитре, я роблю щось подібне до того, що ви робите, щоб розпоряджатися вкладеними поглядами. Я ще не бачив кращого рішення, але мені також було б цікаво дізнатись, чи є воно. Ось ще одна дискусія, яка торкається і цього: groups.google.com/forum/#!topic/backbonejs/3ZFm-lteN-A . Я помітив, що у своєму рішенні ви не берете до уваги сценарій, коли вкладене представлення безпосередньо утилізується. У такому сценарії батьківський подання все одно буде містити посилання на вкладене подання, навіть якщо вкладений подання буде видалено. Не знаю, чи потрібно це враховувати.
Johnny Oshika,

Що робити, якщо у мене є функціональність, яка відкриває і закриває той самий вигляд. У мене є кнопки вперед і назад. Якщо я закликаю dispose, він видалить елемент із DOM. Чи повинен я весь час зберігати погляд у пам’яті?
dagda1

1
Привіт fisherwebdev. Ви також можете використовувати цю техніку з Backbone.View.extend, але вам потрібно буде ініціалізувати this.bindings у методі BaseView.initialize. Проблема в тому, що якщо у вашому успадкованому поданні реалізований власний метод ініціалізації, то йому потрібно буде явно викликати метод ініціалізації BaseView. Я пояснив цю проблему більш детально тут: stackoverflow.com/a/7736030/188740
Джонні Oshika

2
Привіт SunnyRed, я оновив свою відповідь, щоб краще відображати мою причину руйнування поглядів. У Backbone я не бачу причин перезавантажувати сторінку після запуску програми, тому моя односторінкова програма стала досить великою. Оскільки користувачі взаємодіють з моїм додатком, я постійно рендерірую різні розділи сторінки (наприклад, перемикаюся з деталей на редагування подання), тому мені стає набагато простіше завжди створювати нові подання, незалежно від того, чи був цей розділ відтворений раніше чи ні. Моделі, з іншого боку, представляють бізнес-об'єкти, тому я б модифікував їх, лише якщо об'єкт дійсно змінився.
Johnny Oshika

8

Це загальний стан. Якщо ви створюєте нове представлення кожного разу, усі старі представлення все одно будуть прив'язані до всіх подій. Одне, що ви можете зробити, це створити у своєму поданні функцію, яка називається detatch:

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

Потім, перш ніж створювати новий вигляд, обов’язково зателефонуйте detatchдо старого подання.

Звичайно, як ви вже згадували, ви завжди можете створити один вигляд "деталей" і ніколи його не змінювати. Ви можете прив’язатись до події "зміни" на моделі (з подання), щоб повторно відтворити себе. Додайте це до свого ініціалізатора:

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

Це призведе до того, що область деталей буде рендерироваться КОЖНОГО разу, коли в модель вносяться зміни. Ви можете отримати більш точну деталізацію, спостерігаючи за однією властивістю: "change: propName".

Звичайно, для цього потрібна загальна модель, на яку посилається пункт Перегляд, а також подання списку вищого рівня та подання деталей.

Сподіваюся, це допомагає!


1
М-м-м-м, я зробив щось у тому напрямку, який ви запропонували, але у мене все ще є проблеми: наприклад, this.model.unbind()неправильний для мене, оскільки він розв’язує всі події з цієї моделі, включаючи події щодо інших поглядів того самого користувача. Більше того, щоб викликати detachфункцію, мені потрібно зберегти статичне посилання на подання, і мені це зовсім не подобається. Підозрюю, все ще є щось, чого я не розумів ...
solendil

6

Щоб виправити зв'язування подій кілька разів,

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

Використовуючи наведений вище рядок перед створенням нового представлення з маршруту, вирішив проблему, яку я мав із зомбі-переглядами.


Тут є багато дуже хороших, детальних відповідей. Я точно маю намір вивчити деякі пропозиції ViewManger. Однак цей був просто простий, і він ідеально для мене працює, тому що всі мої подання - це всі панелі з методами close (), де я можу просто розв’язати події. Дякую Ашан
netpoetica

2
Здається, я не можу повторити візуалізацію після
відв’язування

@FlyingAtom: Навіть я не можу повторно відтворити подання після розв’язування. Ви знайшли якийсь спосіб це зробити?
Raeesaa

переглянути. $ el.removeData (). unbind ();
Олександр Міллс

2

Я думаю, що більшість людей, починаючи з Backbone, створить подання, як у вашому коді:

var view = new UserDetailView({model:this.model});

Цей код створює зомбі-вигляд, оскільки ми можемо постійно створювати новий вигляд, не очищаючи існуючий вигляд. Однак не зручно викликати view.dispose () для всіх Backbone Views у вашому додатку (особливо якщо ми створюємо подання у циклі 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


Останнім часом я активно працюю з магістраллю, і це, здається, є найбільш доопрацьованим способом обробки зомбі-поглядів під час створення або повторного використання поглядів. Я зазвичай беру приклади Деріка Бейлі, але в цьому випадку це здається більш гнучким. Моє питання полягає в тому, чому не більше людей використовують цю техніку?
MFD3000,

може тому, що він фахівець з Backbone :). Я думаю, що ця техніка досить проста і досить безпечна у використанні, я використовував її і поки не маю проблем :)
thomasdao

0

Однією з альтернатив є прив’язка, на відміну від створення серії нових поглядів, а потім розв’язування цих поглядів. Ви б виконали це, роблячи щось на зразок:

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 іншому користувачеві (тобто, зміните його атрибути), він може запустити функцію візуалізації у поданні з новими атрибутами.

Одна проблема полягає в тому, що це розриває посилання на оригінальну модель. Ви можете обійти це, використовуючи об'єкт колекції, або встановивши модель користувача як атрибут viewmodel. Тоді це буде доступно у поданні як myview.model.get ("модель").


1
Забруднення глобального масштабу ніколи не є хорошою ідеєю. Чому б вам створити екземпляр BB.Models та BB.Views у просторі імен вікна?
Вернон,

0

Використовуйте цей метод для очищення з пам'яті дочірніх і поточних подань.

//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);
       }
  });
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.