Backbone.jsでサブビューをレンダリングして追加する方法


133

ネストされたビューのセットアップがあり、アプリケーションで多少深くなる可能性があります。サブビューを初期化、レンダリング、および追加する方法はたくさんありますが、一般的な方法は何なのでしょうか。

ここに私が考えたカップルがあります:

initialize : function () {

    this.subView1 = new Subview({options});
    this.subView2 = new Subview({options});
},

render : function () {

    this.$el.html(this.template());

    this.subView1.setElement('.some-el').render();
    this.subView2.setElement('.some-el').render();
}

長所:追加で正しいDOM順序を維持することを心配する必要はありません。ビューは早い段階で初期化されるため、render関数では一度にすべてを実行する必要はありません。

短所:コストがかかる可能性のあるredelegateEvents()を強制されますか?親ビューのレンダリング関数は、発生する必要のあるすべてのサブビューレンダリングで乱雑になりますか?tagName要素のを設定する機能がないため、テンプレートは正しいtagNamesを維持する必要があります。

別の方法:

initialize : function () {

},

render : function () {

    this.$el.empty();

    this.subView1 = new Subview({options});
    this.subView2 = new Subview({options});

    this.$el.append(this.subView1.render().el, this.subView2.render().el);
}

長所:イベントを再委任する必要はありません。空のプレースホルダーのみを含むテンプレートは必要なく、tagNameはビューで定義された状態に戻ります。

短所:正しい順序で追加する必要があります。親ビューのレンダリングは、サブビューレンダリングによってまだ乱雑です。

onRenderイベント:

initialize : function () {
    this.on('render', this.onRender);
    this.subView1 = new Subview({options});
    this.subView2 = new Subview({options});
},

render : function () {

    this.$el.html(this.template);

    //other stuff

    return this.trigger('render');
},

onRender : function () {

    this.subView1.setElement('.some-el').render();
    this.subView2.setElement('.some-el').render();
}

長所:サブビューのロジックがビューのrender()メソッドから分離されました。

onRenderイベント:

initialize : function () {
    this.on('render', this.onRender);
},

render : function () {

    this.$el.html(this.template);

    //other stuff

    return this.trigger('render');
},

onRender : function () {
    this.subView1 = new Subview();
    this.subView2 = new Subview();
    this.subView1.setElement('.some-el').render();
    this.subView2.setElement('.some-el').render();
}

私はいくつかの種類を組み合わせて、これらすべての例でさまざまなプラクティスの束を一致させました(それで申し訳ありません)。そして、あなたは何をしませんか?

実践の要約:

  • initializeまたはでサブビューをインスタンス化しrenderますか?
  • すべてのサブビューレンダリングロジックをで実行renderonRenderますか?
  • setElementまたは使用しますかappend/appendTo

私は削除せずに新しいものに注意します、そこにメモリリークがありました。
vimdude

1
心配しないでください、私は子供をクリーンアップするcloseメソッドとを持っていonCloseますが、最初にそれらをインスタンス化してレンダリングする方法に興味があります。
イアンストームテイラー

3
@abdelsaid:JavaScriptでは、GCがメモリの割り当て解除を処理します。deleteJSのdeleteC ++ と同じではありません。あなたが私に尋ねると、それは非常に貧弱な名前のキーワードです。
マイクベイリー

@MikeBanteguiはそれを取得しましたが、JSでメモリを解放するためにnullを割り当てるだけでよいことを除いて、Javaと同じです。私の意味を明確にするために、内部に新しいオブジェクトを含むループを作成して、メモリを監視してみてください。もちろん、GCはそれに到達しますが、到達する前にメモリを失います。この場合、何度も呼び出される可能性のあるRender。
vimdude 2012

3
私は初心者のバックボーン開発者です。例1でイベントの再委任が必要な理由を誰かが説明できますか?(それとも私自身の質問でこれを尋ねるべきですか?)ありがとうございます。
ピラウ

回答:


58

私は一般的にいくつかの異なるソリューションを見たり使用したりしました:

解決策1

var OuterView = Backbone.View.extend({
    initialize: function() {
        this.inner = new InnerView();
    },

    render: function() {
        this.$el.html(template); // or this.$el.empty() if you have no template
        this.$el.append(this.inner.$el);
        this.inner.render();
    }
});

var InnerView = Backbone.View.extend({
    render: function() {
        this.$el.html(template);
        this.delegateEvents();
    }
});

これは最初の例と似ていますが、いくつか変更点があります。

  1. サブ要素を追加する順序が重要です
  2. 外部ビューには、内部ビューに設定されるhtml要素が含まれていません(つまり、内部ビューでtagNameを指定できます)
  3. render() 内部ビューの要素がDOMに配置された後に呼び出されます。これは、内部ビューの要素が render()メソッドが他の要素の位置/サイズに基づいてページに配置/サイズ設定している(これは私の経験では一般的な使用例です)

解決策2

var OuterView = Backbone.View.extend({
    initialize: function() {
        this.render();
    },

    render: function() {
        this.$el.html(template); // or this.$el.empty() if you have no template
        this.inner = new InnerView();
        this.$el.append(this.inner.$el);
    }
});

var InnerView = Backbone.View.extend({
    initialize: function() {
        this.render();
    },

    render: function() {
        this.$el.html(template);
    }
});

解決策2は見た目はきれいに見えるかもしれませんが、私の経験でいくつかの奇妙なことを引き起こし、パフォーマンスに悪影響を及ぼしました。

いくつかの理由から、私は通常Solution 1を使用します。

  1. 私の意見の多くは、彼らのDOMにすでに存在していることに依存しています render()メソッドの
  2. 外側のビューが再レンダリングされる場合、ビューを再初期化する必要はありません。再初期化すると、メモリリークが発生し、既存のバインディングに異常な問題が発生する可能性があります

が呼び出されるnew View()たびに初期化している場合render()、その初期化はdelegateEvents()とにかく呼び出されることに注意してください。だから、あなたが表現したように、それは必ずしも「詐欺」ではないはずです。


1
これらのソリューションは、そうでない場合は、ガベージコレクション妨げるビューでカスタムクリーンアップを行う際に不可欠かもしれView.removeを呼び出すサブビューツリー、アップ作業のどちらも
ドミニク

31

これはバックボーンによくある問題であり、私の経験では、この質問に対する満足のいく答えはありません。特に、この使用例がどれほど一般的であるにも関わらず、ガイダンスがほとんどないため、あなたの不満を共有します。そうは言っても、私は通常、2番目の例に似たものを使用します。

まず、イベントの再委任が必要なものはすべて手放します。Backboneのイベント駆動型ビューモデルは最も重要なコンポーネントの1つであり、アプリケーションが重要でないためにその機能を失うことは、プログラマーの口に悪口を残すことになります。だからスクラッチナンバーワン。

3番目の例については、これは従来のレンダリングプラクティスの最終段階にすぎず、あまり意味がありません。おそらく、実際のイベントトリガーを実行している場合(つまり、不自然な " onRender"イベントではない場合)、それらのイベントをrenderそれ自体にバインドするだけの価値があります。あなたが見つけた場合はrender扱いにくく、複雑になってきて、あなたもいくつかのサブビューを持っています。

2つ目の例に戻ります。これは、おそらく3つの悪のうちの小さい方です。PDF版の42ページにある、Recipes With Backboneから抜粋したコードの例を次に示します。

...
render: function() {
    $(this.el).html(this.template());
    this.addAll();
    return this;
},
  addAll: function() {
    this.collection.each(this.addOne);
},
  addOne: function(model) {
    view = new Views.Appointment({model: model});
    view.render();
    $(this.el).append(view.el);
    model.bind('remove', view.remove);
}

これはあなたの第二の例よりもわずかに、より高度な設定である:彼らは、関数のセットをspecifiy、addAllそしてaddOne、汚い仕事をこと。このアプローチは実行可能だと思います(そして私は確かにそれを使用しています)。しかし、それでも奇妙な後味が残ります。(これらすべての舌の隠喩を許してください。)

正しい順序で追加するという点については、厳密に追加している場合は、それが制限になります。ただし、考えられるすべてのテンプレートスキームを検討してください。おそらく、適切なサブビューを保持する新しい(DOM)要素にできるプレースホルダー要素(たとえば、空divul)をreplaceWith実際に望んでいるでしょう。追記だけが解決策ではありませんし、それだけ気にすれば注文の問題を確実に回避できますが、それがあなたをつまずかせているなら、あなたはデザインの問題を抱えていると思います。サブビューにはサブビューを含めることができますが、適切であればサブビューに含める必要があります。このようにすると、かなりツリー構造のような構造になります。つまり、各サブビューがすべてのサブビューを順番に追加してから、親ビューが次のサブビューを追加していきます。

残念ながら、ソリューション#2は、すぐに使えるバックボーンを使用するために期待できる最高のものです。サードパーティのライブラリをチェックアウトすることに興味がある場合、私が調べた(まだ実際に遊ぶ時間はありませんでした)ライブラリはBackbone.LayoutManagerです。しかし、彼らと同様に、これらと同様の問題について最近議論があります。


4
最後から2番目の行model.bind('remove', view.remove);--Appointmentの初期化関数でそれを行って、それらを別々に保つべきではありませんか?
atp

2
状態を保持しているため、親がレンダリングするたびにビューを再インスタンス化できない場合はどうでしょうか?
mor

この狂気をすべて止めて、Backbone.subviewsプラグインを使用してください!
Brave Dave

6

これはまだ言及されていないことに驚いたが、私はマリオネットの使用を真剣に検討したい。

これは、特定のビュータイプ(を含む、バックボーンアプリケーションにもう少し構造を適用しListViewItemViewRegionおよびLayout)、適切な添加ControllerSをより多く。

これはGithub上のプロジェクトと、バックボーンの基礎の本にあるAddy Osmaniによる優れたガイドです。


3
これは質問の答えにはなりません。
Ceasar Bautista、2015

2
@CeasarBautista私はこれを達成するためにマリオネットを使用する方法には触れませんが、マリオネットは確かに上記の問題を解決しない
ダナ・ウッドマン

4

私は、この問題に対する非常に包括的な解決策を持っています。これにより、コレクション内のモデルを変更でき、そのビューのみが(コレクション全体ではなく)再レンダリングされます。また、close()メソッドによるゾンビビューの削除も処理します。

var SubView = Backbone.View.extend({
    // tagName: must be implemented
    // className: must be implemented
    // template: must be implemented

    initialize: function() {
        this.model.on("change", this.render, this);
        this.model.on("close", this.close, this);
    },

    render: function(options) {
        console.log("rendering subview for",this.model.get("name"));
        var defaultOptions = {};
        options = typeof options === "object" ? $.extend(true, defaultOptions, options) : defaultOptions;
        this.$el.html(this.template({model: this.model.toJSON(), options: options})).fadeIn("fast");
        return this;
    },

    close: function() {
        console.log("closing subview for",this.model.get("name"));
        this.model.off("change", this.render, this);
        this.model.off("close", this.close, this);
        this.remove();
    }
});
var ViewCollection = Backbone.View.extend({
    // el: must be implemented
    // subViewClass: must be implemented

    initialize: function() {
        var self = this;
        self.collection.on("add", self.addSubView, self);
        self.collection.on("remove", self.removeSubView, self);
        self.collection.on("reset", self.reset, self);
        self.collection.on("closeAll", self.closeAll, self);
        self.collection.reset = function(models, options) {
            self.closeAll();
            Backbone.Collection.prototype.reset.call(this, models, options);
        };
        self.reset();
    },

    reset: function() {
        this.$el.empty();
        this.render();
    },

    render: function() {
        console.log("rendering viewcollection for",this.collection.models);
        var self = this;
        self.collection.each(function(model) {
            self.addSubView(model);
        });
        return self;
    },

    addSubView: function(model) {
        var sv = new this.subViewClass({model: model});
        this.$el.append(sv.render().el);
    },

    removeSubView: function(model) {
        model.trigger("close");
    },

    closeAll: function() {
        this.collection.each(function(model) {
            model.trigger("close");
        });
    }
});

使用法:

var PartView = SubView.extend({
    tagName: "tr",
    className: "part",
    template: _.template($("#part-row-template").html())
});

var PartListView = ViewCollection.extend({
    el: $("table#parts"),
    subViewClass: PartView
});

2

サブビューの作成とレンダリングについては、このミックスインを確認してください。

https://github.com/rotundasoftware/backbone.subviews

これは、レンダリングの順序、イベントを再委任する必要がないなど、このスレッドで説明されている多くの問題に対処する最小限のソリューションです。コレクションビューの場合(コレクション内の各モデルが1つのサブビュー)は別のトピックです。私がその場合に知っている最も一般的な解決策は、MarionetteCollectionViewです。


0

上記の解決策はあまり好きではありません。レンダーメソッドで手動で作業する必要がある各ビューよりも、この構成の方を好みます。

  • views ビュー定義のオブジェクトを返す関数またはオブジェクトにすることができます
  • 親の.removeが呼び出されると、.removeネストされた子の最下位から呼び出されます(サブサブサブビューからずっと)
  • デフォルトでは、親ビューは自身のモデルとコレクションを渡しますが、オプションを追加してオーバーライドできます。

次に例を示します。

views: {
    '.js-toolbar-left': CancelBtnView, // shorthand
    '.js-toolbar-right': {
        view: DoneBtnView,
        append: true
    },
    '.js-notification': {
        view: Notification.View,
        options: function() { // Options passed when instantiating
            return {
                message: this.state.get('notificationMessage'),
                state: 'information'
            };
        }
    }
}

0

バックボーンは、この問題や他の多くの問題に関して「一般的な」慣行がないように意図的に構築されました。これは、可能な限りピノピノを使用しないことを意味します。理論的には、バックボーンでテンプレートを使用する必要さえありません。renderビューの関数でjavascript / jqueryを使用して、ビュー内のすべてのデータを手動で変更できます。より極端にするために、特定のrender機能を1つも必要としません。renderFirstNameDOMの名を更新する関数と、DOMの姓を更新する関数を呼び出すことができますrenderLastName。このアプローチをとれば、パフォーマンスの点ではるかに優れており、手動でイベントを再び委任する必要はありません。このコードは、それを読んでいる人にとっても理にかなっています(ただし、コードが長くなり、面倒になります)。

ただし、通常はテンプレートを使用して、ビュー全体を破壊して再構築するだけの欠点はありません。それ以外のことを行うために質問者が発生することはなかったため、レンダーコールごとにサブビューになります。だから、ほとんどの人が遭遇するほとんどすべての状況でそうするのです。そしてそれが、独断的なフレームワークがこれをデフォルトの動作にする理由です。


0

レンダリングされたサブビューを変数としてメインテンプレートに変数として注入することもできます。

最初にサブビューをレンダリングし、次のようにHTMLに変換します。

var subview1 = $(subview1.render.el).html(); var subview2 = $(subview2.render.el).html();

(そのようsubview1 + subview2にして、ループで使用するときのようにビューを動的に文字列連結して、次のようなマスターテンプレートに渡すこともできます。 ... some header stuff ... <%= sub1 %> <%= sub2 %> ... some footer stuff ...

そして最後に次のように注入します:

this.$el.html(_.template(MasterTemplate, { sub1: subview1, sub2: subview2 } ));

サブビュー内のイベントについて:サブビュー内ではなく、このアプローチでは親(masterView)で接続する必要がある可能性が最も高くなります。


0

子ビューを適切に削除することも確認する次のアプローチを使用するのが好きです。これはAddy Osmani の本の例です。

Backbone.View.prototype.close = function() {
    if (this.onClose) {
        this.onClose();
    }
    this.remove(); };

NewView = Backbone.View.extend({
    initialize: function() {
       this.childViews = [];
    },
    renderChildren: function(item) {
        var itemView = new NewChildView({ model: item });
        $(this.el).prepend(itemView.render());
        this.childViews.push(itemView);
    },
    onClose: function() {
      _(this.childViews).each(function(view) {
        view.close();
      });
    } });

NewChildView = Backbone.View.extend({
    tagName: 'li',
    render: function() {
    } });

0

コストがかかるため、イベントを再委任する必要はありません。下記参照:

    var OuterView = Backbone.View.extend({
    initialize: function() {
        this.inner = new InnerView();
    },

    render: function() {
        // first detach subviews            
        this.inner.$el.detach(); 

        // now can set html without affecting subview element's events
        this.$el.html(template);

        // now render and attach subview OR can even replace placeholder 
        // elements in template with the rendered subview element
        this.$el.append(this.inner.render().el);

    }
});

var InnerView = Backbone.View.extend({
    render: function() {
        this.$el.html(template);            
    }
});
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.