Backbone.js: tái tạo hoặc tạo lại chế độ xem?


83

Trong ứng dụng web của mình, tôi có danh sách người dùng trong bảng bên trái và ngăn chi tiết người dùng ở bên phải. Khi quản trị viên nhấp vào một người dùng trong bảng, thông tin chi tiết của nó sẽ được hiển thị ở bên phải.

Tôi có UserListView và UserRowView ở bên trái và UserDetailView ở bên phải. Mọi thứ đều có tác dụng, nhưng tôi có một hành vi kỳ lạ. Nếu tôi nhấp vào một số người dùng ở bên trái, sau đó nhấp vào xóa trên một trong số họ, tôi sẽ nhận được các hộp xác nhận javascript liên tiếp cho tất cả người dùng đã được hiển thị.

Có vẻ như các ràng buộc sự kiện của tất cả các chế độ xem được hiển thị trước đó vẫn chưa bị xóa, điều này dường như là bình thường. Tôi không nên tạo UserDetailView mới mỗi lần trên UserRowView? Tôi có nên duy trì một chế độ xem và thay đổi mô hình tham chiếu của nó không? Tôi có nên theo dõi chế độ xem hiện tại và xóa nó trước khi tạo một chế độ xem mới không? Tôi hơi lạc lõng và mọi ý tưởng sẽ được hoan nghênh. Cảm ơn bạn !

Đây là mã của chế độ xem bên trái (hiển thị hàng, sự kiện nhấp chuột, tạo chế độ xem bên phải)

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

Và mã cho chế độ xem bên phải (nút xóa)

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

Câu trả lời:


28

Tôi đã viết blog về điều này gần đây và cho thấy một số điều tôi làm trong ứng dụng của mình để xử lý các tình huống sau:

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


1
Tại sao không chỉ delete viewtrong bộ định tuyến?
Trantor Liu

Tôi đã tán thành câu trả lời của bạn, nhưng sẽ thực sự có lợi nếu có các phần liên quan của bài đăng blog bên trong chính câu trả lời vì đó là mục tiêu ở đây.
Emile Bergeron

136

Tôi luôn hủy và tạo các lượt xem vì khi ứng dụng trang đơn của tôi ngày càng lớn hơn, việc lưu giữ các lượt xem trực tiếp không sử dụng trong bộ nhớ chỉ để tôi có thể sử dụng lại chúng sẽ trở nên khó duy trì.

Đây là phiên bản đơn giản của kỹ thuật mà tôi sử dụng để dọn dẹp Chế độ xem của mình nhằm tránh rò rỉ bộ nhớ.

Đầu tiên tôi tạo một BaseView mà tất cả các khung nhìn của tôi đều kế thừa. Ý tưởng cơ bản là Chế độ xem của tôi sẽ giữ một tham chiếu đến tất cả các sự kiện mà nó được đăng ký, để khi đến lúc hủy bỏ Chế độ xem, tất cả các ràng buộc đó sẽ tự động không bị ràng buộc. Đây là một ví dụ về triển khai BaseView của tôi:

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;

Bất cứ khi nào Chế độ xem cần liên kết với một sự kiện trên mô hình hoặc bộ sưu tập, tôi sẽ sử dụng phương thức bindTo. Ví dụ:

var SampleView = BaseView.extend({

    initialize: function(){
        this.bindTo(this.model, 'change', this.render);
        this.bindTo(this.collection, 'reset', this.doSomething);
    }
});

Bất cứ khi nào tôi xóa một chế độ xem, tôi chỉ cần gọi phương thức hủy bỏ sẽ tự động dọn dẹp mọi thứ:

var sampleView = new SampleView({model: some_model, collection: some_collection});
sampleView.dispose();

Tôi đã chia sẻ kỹ thuật này với những người đang viết ebook "Backbone.js on Rails" và tôi tin rằng đây là kỹ thuật mà họ đã áp dụng cho cuốn sách.

Cập nhật: 2014-03-24

Kể từ Backone 0.9.9, listeningTo và stopListening đã được thêm vào Sự kiện bằng cách sử dụng cùng một kỹ thuật bindTo và unbindFromAll được trình bày ở trên. Ngoài ra, các cuộc gọi View.remove stopListening tự động, vì vậy việc ràng buộc và hủy liên kết dễ dàng như sau:

var SampleView = BaseView.extend({

    initialize: function(){
        this.listenTo(this.model, 'change', this.render);
    }
});

var sampleView = new SampleView({model: some_model});
sampleView.remove();

Bạn có bất kỳ đề xuất nào về cách loại bỏ các chế độ xem lồng nhau không? Hiện tại, tôi đang làm tương tự với bindTo: gist.github.com/1288947 nhưng tôi đoán có thể làm điều gì đó tốt hơn.
Dmitry Polushkin

Dmitry, tôi làm điều gì đó tương tự như những gì bạn đang làm để loại bỏ các chế độ xem lồng nhau. Tôi vẫn chưa thấy một giải pháp tốt hơn, nhưng tôi cũng muốn biết nếu có một giải pháp. Đây là một cuộc thảo luận khác cũng đề cập đến vấn đề này: groups.google.com/forum/#!topic/backbonejs/3ZFm-lteN-A . Tôi nhận thấy rằng trong giải pháp của bạn, bạn không tính đến trường hợp trong đó chế độ xem lồng nhau được xử lý trực tiếp. Trong trường hợp như vậy, dạng xem mẹ sẽ vẫn giữ một tham chiếu đến dạng xem lồng nhau mặc dù dạng xem lồng nhau bị loại bỏ. Tôi không biết liệu bạn có cần tính đến điều này không.
Johnny Oshika

Điều gì sẽ xảy ra nếu tôi có chức năng mở và đóng cùng một chế độ xem. Tôi có một nút tiến và lùi. Nếu tôi gọi là vứt bỏ, nó sẽ xóa phần tử khỏi DOM. Tôi có nên giữ chế độ xem trong bộ nhớ toàn bộ thời gian không?
dagda1

1
Xin chào ngưwebdev. Bạn cũng có thể sử dụng kỹ thuật này với Backbone.View.extend, nhưng bạn sẽ cần khởi tạo this.bindings trong phương thức BaseView.initialize. Vấn đề với điều này là nếu khung nhìn kế thừa của bạn triển khai phương thức khởi tạo của riêng nó, thì nó sẽ cần gọi phương thức khởi tạo của BaseView một cách rõ ràng. Tôi đã giải thích vấn đề này chi tiết hơn ở đây: stackoverflow.com/a/7736030/188740
Johnny Oshika

2
Xin chào SunnyRed, tôi đã cập nhật câu trả lời của mình để phản ánh rõ hơn lý do hủy lượt xem của tôi. Với Backbone, tôi không thấy lý do gì để tải lại một trang sau khi ứng dụng khởi động, vì vậy ứng dụng một trang của tôi đã trở nên khá lớn. Khi người dùng tương tác với ứng dụng của tôi, tôi liên tục hiển thị các phần khác nhau của trang (ví dụ: chuyển từ chế độ xem chi tiết sang chế độ chỉnh sửa), vì vậy tôi thấy việc luôn tạo chế độ xem mới dễ dàng hơn nhiều, bất kể phần đó đã được hiển thị trước đó hay không phải. Mặt khác, các mô hình đại diện cho các đối tượng kinh doanh, vì vậy tôi sẽ chỉ sửa đổi chúng nếu đối tượng thực sự thay đổi.
Johnny Oshika,

8

Đây là một tình trạng phổ biến. Nếu bạn tạo một chế độ xem mới mỗi lần, tất cả các chế độ xem cũ sẽ vẫn bị ràng buộc với tất cả các sự kiện. Một điều bạn có thể làm là tạo một hàm trên chế độ xem của bạn có tên detatch:

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

Sau đó, trước khi bạn tạo dạng xem mới, hãy đảm bảo gọi detatchtrên dạng xem cũ.

Tất nhiên, như bạn đã đề cập, bạn luôn có thể tạo một chế độ xem "chi tiết" và không bao giờ thay đổi nó. Bạn có thể liên kết với sự kiện "thay đổi" trên mô hình (từ chế độ xem) để tự kết xuất lại. Thêm cái này vào trình khởi tạo của bạn:

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

Làm điều đó sẽ khiến ngăn chi tiết hiển thị lại MỌI lần thay đổi được thực hiện đối với mô hình. Bạn có thể nhận được mức độ chi tiết tốt hơn bằng cách xem một thuộc tính duy nhất: "change: propName".

Tất nhiên, thực hiện điều này đòi hỏi một mô hình chung mà Chế độ xem mục có tham chiếu đến cũng như chế độ xem danh sách cấp cao hơn và chế độ xem chi tiết.

Hi vọng điêu nay co ich!


1
Rất tiếc, tôi đã làm điều gì đó theo hướng bạn đề xuất, nhưng tôi vẫn gặp sự cố: ví dụ: điều this.model.unbind()sai đối với tôi vì nó hủy liên kết tất cả các sự kiện khỏi mô hình này, bao gồm cả các sự kiện liên quan đến các chế độ xem khác của cùng một người dùng. Hơn nữa, để gọi detachhàm, tôi cần phải giữ một tham chiếu tĩnh cho chế độ xem, và tôi thực sự không thích nó. Tôi nghi ngờ vẫn còn một cái gì đó tôi không có undertand ...
solendil

6

Để sửa các sự kiện ràng buộc nhiều lần,

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

Sử dụng dòng trên trước khi khởi tạo Chế độ xem mới từ tuyến đường, đã giải quyết được vấn đề tôi gặp phải với chế độ xem zombie.


Tại đây có rất nhiều câu trả lời rất hay, chi tiết. Tôi chắc chắn có ý định xem xét một số đề xuất ViewManger. Tuy nhiên, cái này rất đơn giản và nó hoạt động hoàn hảo đối với tôi vì Chế độ xem của tôi đều là Bảng điều khiển có phương thức close (), nơi tôi có thể hủy liên kết các sự kiện. Cảm ơn Ashan
netpoetica

2
I cant dường như lại làm sau khi tôi unbind: \
CodeGuru

@FlyingAtom: Ngay cả khi tôi không thể hiển thị lại các lượt xem sau khi hủy liên kết. Bạn đã tìm thấy bất kỳ cách nào để làm điều đó?
Raeesaa

xem. $ el.removeData (). unbind ();
Alexander Mills

2

Tôi nghĩ rằng hầu hết mọi người bắt đầu với Backbone sẽ tạo chế độ xem như trong mã của bạn:

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

Mã này tạo chế độ xem zombie, vì chúng tôi có thể liên tục tạo chế độ xem mới mà không cần xóa chế độ xem hiện có. Tuy nhiên, không thuận tiện khi gọi view.dispose () cho tất cả các Chế độ xem xương sống trong ứng dụng của bạn (đặc biệt nếu chúng tôi tạo các chế độ xem trong vòng lặp)

Tôi nghĩ thời điểm tốt nhất để đặt mã dọn dẹp là trước khi tạo chế độ xem mới. Giải pháp của tôi là tạo một trình trợ giúp để thực hiện việc dọn dẹp này:

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

Sử dụng VM để tạo chế độ xem của bạn sẽ giúp xóa sạch mọi chế độ xem hiện có mà không cần phải gọi view.dispose (). Bạn có thể thực hiện một sửa đổi nhỏ đối với mã của mình từ

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

đến

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

Vì vậy, bạn có muốn sử dụng lại chế độ xem thay vì liên tục tạo nó hay không, miễn là chế độ xem sạch sẽ là được, bạn không cần phải lo lắng. Chỉ cần thay đổi createView để sử dụng lạiView:

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

Mã chi tiết và thuộc tính được đăng tại https://github.com/thomasdao/Backbone-View-Manager


Gần đây, tôi đã làm việc với backbone rộng rãi và đây dường như là phương tiện hữu ích nhất để xử lý các chế độ xem zombie khi xây dựng hoặc sử dụng lại các chế độ xem. Tôi thường làm theo các ví dụ của Derick Bailey, nhưng trong trường hợp này, điều này có vẻ linh hoạt hơn. Câu hỏi của tôi là, tại sao không có nhiều người sử dụng kỹ thuật này?
MFD3000

có lẽ vì anh ấy là chuyên gia về Backbone :). Tôi nghĩ kỹ thuật này khá đơn giản và khá an toàn để sử dụng, tôi đã sử dụng nó và không có vấn đề gì cho đến nay :)
thomasdao

0

Một cách thay thế là ràng buộc, trái ngược với việc tạo một loạt các chế độ xem mới và sau đó hủy liên kết các chế độ xem đó. Bạn sẽ thực hiện điều này bằng cách làm như sau:

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

Bạn sẽ đặt mô hình của myView thành myViewModel, mô hình này sẽ được đặt thành Mô hình người dùng. Bằng cách này, nếu bạn đặt myViewModel thành người dùng khác (tức là thay đổi thuộc tính của nó) thì nó có thể kích hoạt chức năng kết xuất trong chế độ xem với các thuộc tính mới.

Một vấn đề là điều này phá vỡ liên kết với mô hình ban đầu. Bạn có thể giải quyết vấn đề này bằng cách sử dụng đối tượng bộ sưu tập hoặc bằng cách đặt mô hình người dùng làm thuộc tính của mô hình xem. Sau đó, điều này sẽ có thể truy cập được trong chế độ xem dưới dạng myview.model.get ("model").


1
Gây ô nhiễm phạm vi toàn cầu không bao giờ là một ý tưởng hay. Tại sao bạn tạo BB.Models và BB.Views trên không gian tên cửa sổ?
Vernon

0

Sử dụng phương pháp này để xóa các khung nhìn con và các khung nhìn hiện tại khỏi bộ nhớ.

//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);
       }
  });
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.