How to handle initializing and rendering subviews in Backbone.js? Ask Question

How to handle initializing and rendering subviews in Backbone.js? Ask Question

I have three different ways to initialize and render a view and its subviews, and each one of them has different problems. I'm curious to know if there is a better way that solves all of the problems:


Scenario One:

Initialize the children in the parent's initialize function. This way, not everything gets stuck in render so that there is less blocking on rendering.

initialize : function () {

    //parent init stuff

    this.child = new Child();
},

render : function () {

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

    this.child.render().appendTo(this.$('.container-placeholder');
}

The problems:

  • The biggest problem is that calling render on the parent for a second time will remove all of the childs event bindings. (This is because of how jQuery's $.html() works.) This could be mitigated by calling this.child.delegateEvents().render().appendTo(this.$el); instead, but then the first, and the most often case, you're doing more work unnecessarily.

  • By appending the children, you force the render function to have knowledge of the parents DOM structure so that you get the ordering you want. Which means changing a template might require updating a view's render function.


Scenario Two:

Initialize the children in the parent's initialize() still, but instead of appending, use setElement().delegateEvents() to set the child to an element in the parents template.

initialize : function () {

    //parent init stuff

    this.child = new Child();
},

render : function () {

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

    this.child.setElement(this.$('.placeholder-element')).delegateEvents().render();
}

Problems:

  • This makes the delegateEvents() necessary now, which is a slight negative over it only being necessary on subsequent calls in the first scenario.

Scenario Three:

Initialize the children in the parent's render() method instead.

initialize : function () {

    //parent init stuff
},

render : function () {

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

    this.child = new Child();

    this.child.appendTo($.('.container-placeholder').render();
}

Problems:

  • This means that the render function now has to be tied down with all of the initialization logic as well.

  • If I edit the state of one of the child views, and then call render on the parent, a completely new child will be made and all of its current state will be lost. Which also seems like it could get dicey for memory leaks.


Really curious to get your guys' take on this. Which scenario would you use? or is there a fourth magical one that solves all of these problems?

Have you ever kept track of a rendered state for a View? Say a renderedBefore flag? Seems really janky.

ベストアンサー1

This is a great question. Backbone is great because of the lack of assumptions it makes, but it does mean you have to (decide how to) implement things like this yourself. After looking through my own stuff, I find that I (kind of) use a mix of scenario 1 and scenario 2. I don't think a 4th magical scenario exists because, simply enough, everything you do in scenario 1 & 2 must be done.

I think it'd be easiest to explain how I like to handle it with an example. Say I have this simple page broken into the specified views:

ページ内訳

Say the HTML is, after being rendered, something like this:

<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>

Hopefully it's pretty obvious how the HTML matches up with the diagram.

The ParentView holds 2 child views, InfoView and PhoneListView as well as a few extra divs, one of which, #name, needs to be set at some point. PhoneListView holds child views of its own, an array of PhoneView entries.

So on to your actual question. I handle initialization and rendering differently based on the view type. I break my views into two types, Parent views and Child views.

The difference between them is simple, Parent views hold child views while Child views do not. So in my example, ParentView and PhoneListView are Parent views, while InfoView and the PhoneView entries are Child views.

前に述べたように、これら 2 つのカテゴリの最大の違いは、レンダリングが許可されるタイミングです。理想的な世界では、Parentビューは 1 回だけレンダリングされるようにします。モデルが変更されたときの再レンダリングは、子ビューによって処理されます。Child一方、ビューは、他のビューに依存していないため、必要なときにいつでも再レンダリングを許可します。

もう少し詳しく説明すると、ビューの場合、関数で次のことを行うのParentが好みです。initialize

  1. 自分のビューを初期化する
  2. 自分のビューをレンダリングする
  3. 子ビューを作成して初期化します。
  4. 各子ビューにビュー内の要素を割り当てます (たとえば、 がInfoView割り当てられます#info)。

ステップ 1 は、説明の必要もほとんどありません。

ステップ 2 のレンダリングは、子ビューが依存するすべての要素が、割り当てる前にすでに存在するように行われます。こうすることで、すべての子がevents正しく設定されることがわかり、再委任の必要性を心配することなく、ブロックを何度でも再レンダリングできます。renderここでは実際には子ビューは使用せず、子ビューが独自の 内で実行できるようにしますinitialization

ステップ 3 と 4 は、実際には子ビューの作成時に渡すと同時に処理されますel。親が自身のビューのどこに子がコンテンツを配置できるかを決定する必要があると思うので、ここで要素を渡すのが好きです。

レンダリングについては、ビューをかなりシンプルに保つようにしていますParentrender関数には親ビューのレンダリング以外の何も実行させないようにします。イベント委任や子ビューのレンダリングなどは一切行いません。単純なレンダリングだけです。

しかし、これが常に機能するとは限りません。たとえば、上記の例では、#nameモデル内の名前が変更されるたびに要素を更新する必要があります。ただし、このブロックはテンプレートの一部でありParentView、専用のビューで処理されないため、その点Childを回避します。subRenderのみ要素のコンテンツを置き換え#name、要素全体を破棄する必要がなくなります#parent。これはハックのように思えるかもしれませんが、DOM 全体を再レンダリングしたり、要素を再アタッチしたりするよりも、この方法の方が効果的だとわかりました。本当にきれいにしたい場合は、ブロックを処理する新しいChildビュー ( に似たものInfoView)を作成します#name

さてChild、ビューについては、はビューinitializationと非常に似ていますParentが、それ以上のビューは作成されませんChild。つまり、次のようになります。

  1. ビューを初期化する
  2. セットアップは、私が関心のあるモデルの変更をリッスンするようにバインドします
  3. ビューをレンダリングする

Childビューのレンダリングも非常にシンプルで、レンダリングして my のコンテンツを設定するだけです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);
    }
});

You can see my implementation of subRender here. By having changes bound to subRender instead of render, I don't have to worry about blasting away and rebuilding the whole block.

Here's example code for the InfoView block:

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

The binds are the important part here. By binding to my model, I never have to worry about manually calling render myself. If the model changes, this block will re-render itself without affecting any other views.

The PhoneListView will be similar to the ParentView, you'll just need a little more logic in both your initialization and render functions to handle collections. How you handle the collection is really up to you, but you'll at least need to be listening to the collection events and deciding how you want to render (append/remove, or just re-render the whole block). I personally like to append new views and remove old ones, not re-render the whole view.

The PhoneView will be almost identical to the InfoView, only listening to the model changes it cares about.

Hopefully this has helped a little, please let me know if anything is confusing or not detailed enough.

おすすめ記事