ビューとそのサブビューを初期化してレンダリングするには3つの異なる方法があり、それぞれに異なる問題があります。すべての問題を解決するより良い方法があるかどうか知りたいです:
親の初期化関数で子を初期化します。この方法では、すべてがレンダリングでスタックするわけではないため、レンダリングのブロックが少なくなります。
initialize : function () {
//parent init stuff
this.child = new Child();
},
render : function () {
this.$el.html(this.template());
this.child.render().appendTo(this.$('.container-placeholder');
}
問題点:
最大の問題は、親で2回renderを呼び出すと、すべての子イベントバインディングが削除されることです。 (これはjQueryの$.html()
がどのように機能するかによるものです。)代わりにthis.child.delegateEvents().render().appendTo(this.$el);
を呼び出すことで軽減できますが、最初の、そしてほとんどの場合、不必要に多くの作業をしています。
子を追加することにより、render関数に親のDOM構造の知識を持たせ、必要な順序を取得できます。つまり、テンプレートを変更するには、ビューのレンダリング関数を更新する必要がある場合があります。
まだ親のinitialize()
の子を初期化しますが、追加する代わりに、setElement().delegateEvents()
を使用して、子を親テンプレートの要素に設定します。
initialize : function () {
//parent init stuff
this.child = new Child();
},
render : function () {
this.$el.html(this.template());
this.child.setElement(this.$('.placeholder-element')).delegateEvents().render();
}
問題点:
delegateEvents()
が必要になります。これは、最初のシナリオの後続の呼び出しでのみ必要であるという点でわずかにマイナスです。代わりに、親のrender()
メソッドで子を初期化します。
initialize : function () {
//parent init stuff
},
render : function () {
this.$el.html(this.template());
this.child = new Child();
this.child.appendTo($.('.container-placeholder').render();
}
問題点:
これは、レンダリング関数をすべての初期化ロジックと同様に結び付ける必要があることを意味します。
子ビューの1つの状態を編集してから親でrenderを呼び出すと、完全に新しい子が作成され、現在の状態がすべて失われます。これは、メモリリークの危険性もあるようです。
あなたの仲間にこれを理解してもらうことに本当に興味があります。どのシナリオを使用しますか?または、これらすべての問題を解決する4つ目の魔法のようなものはありますか?
ビューのレンダリング状態を追跡したことがありますか? renderedBefore
フラグを言う?本当にジャンキーそうです。
これは素晴らしい質問です。 Backboneは前提条件がないため優れていますが、このようなものを自分で実装する(方法を決定する)必要があるということです。自分のものを調べた後、私はシナリオ1とシナリオ2を組み合わせて使用していることに気付きました。シナリオ1と2で行うすべてのことを十分に行う必要があるため、4番目の魔法のシナリオは存在しません。できた。
私がそれをどのように扱いたいかを例で説明するのが最も簡単だと思います。この単純なページを指定されたビューに分割したとします。
HTMLがレンダリングされた後、次のようになっているとします。
<div id="parent">
<div id="name">Person: Kevin Peel</div>
<div id="info">
First name: <span class="first_name">Kevin</span><br />
Last name: <span class="last_name">Peel</span><br />
</div>
<div>Phone Numbers:</div>
<div id="phone_numbers">
<div>#1: 123-456-7890</div>
<div>#2: 456-789-0123</div>
</div>
</div>
うまくいけば、HTMLが図とどのように一致するかはかなり明白です。
ParentView
は、InfoView
とPhoneListView
の2つの子ビューと、いくつかの余分なdivを保持します。そのうちの1つは#name
を設定する必要があります。 PhoneListView
は、独自の子ビュー、PhoneView
エントリの配列を保持します。
あなたの実際の質問に続きます。ビュータイプに基づいて、初期化とレンダリングを別々に処理します。ビューをParent
ビューとChild
ビューの2つのタイプに分けます。
それらの違いは単純です。Parent
ビューは子ビューを保持しますが、Child
ビューは保持しません。したがって、私の例では、ParentView
およびPhoneListView
ビューはParent
ビューであり、InfoView
およびPhoneView
エントリーはChild
ビューです。
前述したように、これら2つのカテゴリの最大の違いは、レンダリングが許可される場合です。完璧な世界では、Parent
ビューが一度だけレンダリングされるようにします。モデルが変更されたときに再レンダリングを処理するのは、子ビュー次第です。一方、Child
ビューは、それらに依存する他のビューがないため、必要なときにいつでも再レンダリングできます。
もう少し詳しく説明すると、Parent
ビューについては、initialize
関数がいくつかのことをするのが好きです:
InfoView
には#info
が割り当てられます)。ステップ1は自明です。
ステップ2のレンダリングは、子ビューが依存する要素が割り当てられる前に既に存在するように行われます。これを行うことで、すべての子events
が正しく設定されることがわかり、再デリゲートすることを心配せずに、何度でもブロックを再レンダリングできます。ここでは実際にrender
の子ビューはありません。自分のinitialization
内で子ビューを許可します。
ステップ3と4は、子ビューの作成中にel
を渡すと同時に実際に処理されます。ここで要素を渡すのが好きです。親が自分のビューのどこに子がコンテンツを配置できるかを判断する必要があると思うからです。
レンダリングについては、Parent
ビューでかなりシンプルにしようとしています。 render
関数は、親ビューをレンダリングする以外のことは何もしません。イベントの委任、子ビューのレンダリング、なし。単純なレンダリングです。
ただし、これが常に機能するとは限りません。たとえば、上記の私の例では、モデル内の名前が変更されるたびに#name
要素を更新する必要があります。ただし、このブロックはParentView
テンプレートの一部であり、専用のChild
ビューで処理されないため、回避します。 onlyが#name
要素のコンテンツを置き換え、#parent
要素全体を破棄する必要のない、ある種のsubRender
関数を作成します。これはハックのように思えるかもしれませんが、DOM全体を再レンダリングし、要素などを再アタッチすることを心配するよりもうまく機能することが本当にわかりました。本当にきれいにしたい場合は、#name
ブロックを処理する新しいChild
ビュー(InfoView
に類似)を作成します。
現在、Child
ビューの場合、initialization
ビューはParent
ビューと非常によく似ていますが、追加のChild
ビューは作成されません。そう:
Child
ビューのレンダリングも非常にシンプルで、el
のコンテンツをレンダリングして設定するだけです。繰り返しますが、委任やそのようなものをいじることはありません。
ParentView
がどのように見えるかを示すサンプルコードを次に示します。
var ParentView = Backbone.View.extend({
el: "#parent",
initialize: function() {
// Step 1, (init) I want to know anytime the name changes
this.model.bind("change:first_name", this.subRender, this);
this.model.bind("change:last_name", this.subRender, this);
// Step 2, render my own view
this.render();
// Step 3/4, create the children and assign elements
this.infoView = new InfoView({el: "#info", model: this.model});
this.phoneListView = new PhoneListView({el: "#phone_numbers", model: this.model});
},
render: function() {
// Render my template
this.$el.html(this.template());
// Render the name
this.subRender();
},
subRender: function() {
// Set our name block and only our name block
$("#name").html("Person: " + this.model.first_name + " " + this.model.last_name);
}
});
subRender
の実装をここで見ることができます。変更をsubRender
ではなくrender
にバインドすることにより、ブロック全体をブラストして再構築することを心配する必要がなくなります。
InfoView
ブロックのサンプルコードは次のとおりです。
var InfoView = Backbone.View.extend({
initialize: function() {
// I want to re-render on changes
this.model.bind("change", this.render, this);
// Render
this.render();
},
render: function() {
// Just render my template
this.$el.html(this.template());
}
});
ここでは、バインドが重要な部分です。自分のモデルにバインドすることで、自分でrender
を手動で呼び出すことを心配する必要がなくなりました。モデルが変更されると、このブロックは他のビューに影響を与えることなくそれ自体を再レンダリングします。
PhoneListView
はParentView
に似ています。コレクションを処理するには、initialization
関数とrender
関数の両方にもう少しロジックが必要です。コレクションの処理方法は本当にあなた次第ですが、少なくともコレクションイベントをリッスンし、レンダリング方法(ブロック全体の追加/削除、または再レンダリング)を決定する必要があります。個人的には、ビュー全体を再レンダリングするのではなく、新しいビューを追加して古いビューを削除するのが好きです。
PhoneView
は、InfoView
とほぼ同じで、関心のあるモデルの変更のみをリッスンします。
うまくいけば、これが少し助けになったと思います。何かが混乱したり、詳細が十分でない場合はお知らせください。
私にとっては、何らかのフラグを介してビューの初期設定とその後の設定を区別することは、世界で最悪の考えとは思えません。これをクリーンで簡単にするために、バックボーン(ベース)ビューを拡張する独自のビューにフラグを追加する必要があります。
デリックと同じように、これがあなたの質問に直接答えるかどうかは完全にはわかりませんが、少なくともこの文脈で言及する価値があると思います。
これがあなたの質問に直接答えるかどうかはわかりませんが、関連があると思います:
http://lostechies.com/derickbailey/2011/10/11/backbone-js-getting-the-model-for-a-clicked-element/
もちろん、この記事を設定するコンテキストは異なりますが、私が提供する2つのソリューションとそれぞれの長所と短所は、正しい方向に進むはずです。
ケビン・ピールは素晴らしい答えを与えます-私のtl; drバージョンは次のとおりです。
initialize : function () {
//parent init stuff
this.render(); //ANSWER: RENDER THE PARENT BEFORE INITIALIZING THE CHILD!!
this.child = new Child();
},
私がやることは、各子供にアイデンティティを与えることです(Backboneはすでにあなたのためにそれを行っています:cid)
コンテナがレンダリングを実行すると、「cid」と「tagName」を使用してすべての子のプレースホルダーが生成されるため、テンプレートでは、子はコンテナによってどこに配置されるかわかりません。
<tagName id='cid'></tagName>
あなたが使用できるより
Container.render()
Child.render();
this.$('#'+cid).replaceWith(child.$el);
// the rapalceWith in jquery will detach the element
// from the dom first, so we need re-delegateEvents here
child.delegateEvents();
指定されたプレースホルダーは不要で、コンテナは子のDOM構造ではなくプレースホルダーのみを生成します。 CotainerとChildrenはまだ1回だけ独自のDOM要素を生成しています。
私はこのようなビュー間の結合を避けようとしています。私が通常行う方法は2つあります。
基本的に、ルーター関数で親ビューと子ビューを初期化できます。そのため、ビューには互いの知識はありませんが、ルーターがすべてを処理します。
this.parent = new Parent({el: $('.container-placeholder')});
this.child = new Child({el: $('.container-placeholder')});
どちらも同じDOMの知識があり、好きなように注文できます。
サブビューを作成およびレンダリングするための軽量ミックスインを次に示します。これは、このスレッドのすべての問題に対処すると思います。
https://github.com/rotundasoftware/backbone.subviews
このプラグインがとるアプローチは、親ビューがレンダリングされるfirst時間の後にサブビューを作成およびレンダリングすることです。次に、親ビューの後続のレンダリングで、サブビュー要素を$ .detachし、親を再レンダリングしてから、適切な場所にサブビュー要素を挿入し、それらを再レンダリングします。このように、サブビューオブジェクトは後続のレンダリングで再利用され、イベントを再デリゲートする必要はありません。
コレクションビューの場合(コレクション内の各モデルが1つのサブビューで表される場合)はまったく異なり、独自の議論/解決策に値することに注意してください。私がその場合に知っている最も一般的な解決策は、 MarionetteのCollectionView です。
編集:コレクションビューの場合、クリックおよび/またはドラッグアンドドロップに基づいて並べ替えのためにモデルを選択する必要がある場合は、 このUIに重点を置いた実装 も確認してください。