1. ホーム
  2. javascript

[解決済み] Backbone.jsでサブビューの初期化およびレンダリングを処理するには?

2022-04-30 16:18:54

質問

ビューとそのサブビューを初期化してレンダリングする方法が3種類あり、それぞれ異なる問題があります。すべての問題を解決する、より良い方法があるかどうか知りたいのです。


シナリオ1

親のinitialize関数の中で子供を初期化する。こうすることで、すべてがレンダリングに滞留することがなくなり、レンダリング時のブロックが少なくなります。

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); 代わりに、最初の、そして最もよくあるケースは、不必要に多くの作業をすることになります。

  • 子要素を追加することで、レンダー関数に親の DOM 構造に関する知識を持たせ、希望する順序になるようにします。つまり、テンプレートを変更すると、ビューのレンダー関数を更新する必要がある場合があります。


シナリオ2

子を初期化するのは、親の 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() これは、最初のシナリオではその後の呼び出しで必要であったのに対して、少しマイナスです。

シナリオ3

子を初期化するのは、親の 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つ目の魔法のようなシナリオがあるのでしょうか?

Viewのレンダリング状態を記録したことはありますか?例えば renderedBefore フラグ?実に稚拙な感じがします。

解決方法は?

これはいい質問ですね。Backboneは仮定がないので素晴らしいのですが、このようなことを自分で(どのように)実装するかを決めなければならないことを意味します。自分自身のものに目を通した結果、私はシナリオ1とシナリオ2を(なんとなく)混ぜて使っていることがわかりました。シナリオ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 は2つの子ビューを保持しています。 InfoViewPhoneListView と同様に、いくつかの余分なdiv、そのうちの1つ。 #name を設定する必要があります。 PhoneListView の配列で、それ自身の子ビューを保持しています。 PhoneView のエントリーがあります。

では、実際の質問に移ります。 私は、ビューの種類によって、初期化とレンダリングを異なる方法で処理しています。 私はビューを2つのタイプに分割しています。 Parent ビューと Child ビューになります。

両者の違いは簡単です。 Parent は子ビューを保持するのに対し Child ビューにはありません。ですから、私の例では ParentViewPhoneListViewParent を表示するのに対し InfoViewPhoneView のエントリは Child のビューを表示します。

前にも述べたように、この2つのカテゴリーの最大の違いは、レンダリングが許可されるタイミングです。完璧な世界では、私は Parent ビューは一度だけレンダリングされます。モデルが変更された場合の再レンダリングは、その子ビューに任されます。 Child 一方、ビューは、他のビューに依存しないので、必要なときにいつでも再レンダリングできるようにしています。

もう少し詳しく説明すると Parent ビューが好きです。 initialize 関数は、いくつかのことを行うことができます。

  1. 自作ビューの初期化
  2. 自作ビューのレンダリング
  3. 任意の子ビューを作成し、初期化する。
  4. 各子ビューに、私のビュー内の要素を割り当てます (例. InfoView には #info ).

ステップ1は、かなり自己説明的です。

ステップ2のレンダリングでは、子ビューが依存する要素がすでに存在していることを確認してから、それを割り当てようとします。こうすることで、すべての子ビューの events は正しく設定され、再委任の心配をせずに何度でもブロックを再レンダリングすることができます。私は、実際には render 子ビューは、各自の initialization .

ステップ3と4は、実は同時に処理されます。 el を子ビューの作成時に入力します。ここで要素を渡すのが好きなのは、親が自身のビューのどこに子コンテンツを置くことができるかを決めるべきだと思っているからです。

レンダリングについては、なるべくシンプルに Parent ビューになります。私は render 関数は、親ビューをレンダリングすること以外は何もしません。イベントデリゲーションも、子ビューのレンダリングも、何もしません。ただ単純にレンダリングするだけです。

しかし、これがいつもうまくいくとは限りません。例えば、上の例では #name 要素は、モデル内の名前が変わるたびに更新される必要があります。しかし、このブロックは ParentView テンプレートで処理されることはなく、専用の Child を表示させることができるので、それを回避しています。私は、ある種の subRender という関数があります。 のみ の内容を置き換えます。 #name 要素全体をゴミ箱に入れる必要はありません。 #parent 要素を使用します。これはハックに見えるかもしれませんが、DOM全体を再レンダリングして要素を付け直したりすることを心配するよりも、この方法が効果的だと実感しています。もし本当にきれいにしたいのであれば、私は新しい Child ビューを表示します。 InfoView を処理するようなものです。 #name ブロックを作成します。

では、次に Child ビューでは initialization とはかなり似ています。 Parent ビューを作成しないだけで、さらに Child ビューになります。 だから

  1. ビューを初期化する
  2. 気になるモデルの変更をリッスンするバインドをセットアップする
  3. ビューをレンダリングする

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 の両方でもう少しロジックが必要になります。 initializationrender 関数を使用して、コレクションを処理することができます。コレクションをどのように扱うかはあなた次第ですが、少なくともコレクションイベントをリッスンして、どのようにレンダリングするか(追加/削除、またはブロック全体を再レンダリング)を決定する必要があります。私自身は、ビュー全体を再レンダリングするのではなく、新しいビューを追加し、古いビューを削除するのが好きです。

は、その PhoneView とほぼ同じになります。 InfoView しかし、気になるモデルの変更だけをリスニングします。

少しでも参考になれば幸いです。わかりにくいところや、説明が不十分なところがあれば教えてください。