Backbone.js:ビューを再作成または再作成しますか?


83

私のWebアプリケーションでは、左側のテーブルにユーザーリストがあり、右側にユーザ​​ー詳細ペインがあります。管理者がテーブル内のユーザーをクリックすると、その詳細が右側に表示されます。

左側にUserListViewとUserRowViewがあり、右側にUserDetailViewがあります。物事は一種の仕事ですが、私は奇妙な行動をしています。左側の一部のユーザーをクリックしてから、そのうちの1つをクリックすると、表示されているすべてのユーザーのJavaScript確認ボックスが連続して表示されます。

以前に表示されたすべてのビューのイベントバインディングが削除されていないようですが、これは正常のようです。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;
    }
})

回答:


28

私は最近これについてブログを書き、これらのシナリオを処理するためにアプリで行ういくつかのことを示しました。

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


1
なぜdelete viewルーターだけではないのですか?
Trantor Liu 2012

私はあなたの答えに賛成しましたが、それがここでの目標であるため、ブログ投稿の関連部分を答え自体の中に含めることは本当に有益です。
エミールベルジェロン2016年

136

シングルページアプリがどんどん大きくなるにつれて、未使用のライブビューをメモリに保持して再利用できるようにするため、ビューを破棄して作成するのは常に困難です。

これは、メモリリークを回避するためにビューをクリーンアップするために使用する手法の簡略版です。

最初に、すべてのビューが継承する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.jsonRails」の電子ブックを書いている人々と共有しました。これは、彼らがこの本に採用したテクニックだと思います。

更新: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();

ネストされたビューを破棄する方法について何か提案はありますか?現在、bindTo:gist.github.com/1288947と同様のことを行っていますが、もっと良いことをすることは可能だと思います。
Dmitry Polushkin 2011年

ドミトリー、ネストされたビューを破棄するためにあなたがしているのと同じようなことをします。より良い解決策はまだ見ていませんが、解決策があるかどうかも知りたいと思います。これにも触れている別のディスカッションがあります:groups.google.com/forum / #!topic / backbonejs / 3ZFm-lteN-A。あなたのソリューションでは、ネストされたビューが直接破棄されるシナリオを考慮していないことに気づきました。このようなシナリオでは、ネストされたビューが破棄されても、親ビューはネストされたビューへの参照を保持します。あなたがこれを説明する必要があるかどうかはわかりません。
ジョニー・大鹿

同じビューを開いたり閉じたりする機能がある場合はどうなりますか。進むボタンと戻るボタンがあります。disposeを呼び出すと、要素がDOMから削除されます。ビューを常にメモリに保持する必要がありますか?
dagda1 2011年

1
こんにちはfisherwebdev。この手法はBackbone.View.extendでも使用できますが、BaseView.initializeメソッドでthis.bindingsを初期化する必要があります。これに伴う問題は、継承されたビューが独自の初期化メソッドを実装している場合、BaseViewの初期化メソッドを明示的に呼び出す必要があることです。私はここでこの問題をより詳細に説明しました:stackoverflow.com/a/7736030/188740
Johnny

2
こんにちはSunnyRed、ビューを破棄する理由をより適切に反映するように回答を更新しました。Backboneを使用すると、アプリの起動後にページをリロードする理由がないため、シングルページアプリがかなり大きくなりました。ユーザーがアプリを操作すると、ページのさまざまなセクションが常に再レンダリングされるため(たとえば、詳細ビューから編集ビューに切り替えるなど)、そのセクションが以前にレンダリングされたかどうかに関係なく、常に新しいビューを作成する方がはるかに簡単です。そうではありません。一方、モデルはビジネスオブジェクトを表すため、オブジェクトが実際に変更された場合にのみモデルを変更します。
ジョニー・大鹿

8

これは一般的な状態です。毎回新しいビューを作成する場合、すべての古いビューは引き続きすべてのイベントにバインドされます。実行できることの1つは、ビューに次の関数を作成することですdetatch

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

次に、新しいビューを作成する前に、必ずdetatch古いビューを呼び出してください。

もちろん、あなたが言ったように、あなたはいつでも1つの「詳細」ビューを作成することができ、それを変更することは決してありません。モデルの「変更」イベントに(ビューから)バインドして、自分自身を再レンダリングできます。これを初期化子に追加します。

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

これを行うと、モデルに変更が加えられるたびに詳細ペインが再レンダリングされます。「change:propName」という単一のプロパティを監視することで、より細かい粒度を取得できます。

もちろん、これを行うには、アイテムビューが参照する共通モデルと、上位レベルのリストビューおよび詳細ビューが必要です。

お役に立てれば!


1
うーん、私はあなたが提案した線に沿って何かをしましたが、まだ問題があります。たとえば、this.model.unbind()同じユーザーの他のビューに関するイベントを含む、このモデルからのすべてのイベントのバインドが解除されるため、私にとっては間違っています。さらに、detach関数を呼び出すために、ビューへの静的参照を保持する必要があり、私はそれがまったく好きではありません。私はundertandいない何かが...まだだ疑い
solendil

6

複数回バインドされているイベントを修正するには、

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

ルートから新しいビューをインスタンス化する前に上記の行を使用して、ゾンビビューで発生した問題を解決しました。


ここには非常に優れた詳細な回答がたくさんあります。私は間違いなくViewMangerの提案のいくつかを調べるつもりです。ただし、これは非常に単純で、ビューはすべてclose()メソッドを備えたパネルであり、イベントのバインドを解除できるため、完全に機能します。ありがとうアシャン
netpoetica 2013年

2
バインドを解除した後、再レンダリングできないようです:\
CodeGuru 2013年

@FlyingAtom:バインドを解除した後、ビューを再レンダリングすることはできません。それを行う方法を見つけましたか?
Raeesaa 2014年

view。$ el.removeData()。unbind();
アレクサンダーミルズ

2

ほとんどの人は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に投稿されています


私は最近バックボーンを広範囲に使用しており、これはビューを構築または再利用するときにゾンビビューを処理するための最も具体的な手段のようです。私は通常、Derick Baileyの例に従いますが、この場合、これはより柔軟に見えます。私の質問は、なぜこのテクニックを使用する人が増えるのかということです。
MFD3000 2012

おそらく彼はバックボーンの専門家だからです:)。このテクニックは非常にシンプルで安全に使用できると思います。私はこれを使用していて、今のところ問題はありません:)
thomasdao 2012

0

1つの代替方法は、一連の新しいビューを作成してからそれらのビューのバインドを解除するのではなく、バインドすることです。あなたは次のようなことをしてこれを達成するでしょう:

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を別のユーザーに設定した場合(つまり、その属性を変更した場合)、新しい属性を使用してビューでレンダリング関数をトリガーできます。

1つの問題は、これが元のモデルへのリンクを壊すことです。これは、コレクションオブジェクトを使用するか、ユーザーモデルをビューモデルの属性として設定することで回避できます。次に、これはビューでmyview.model.get( "model")としてアクセスできます。


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);
       }
  });
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.