5

ビューをレンダリングしているが、そのビューが依存するモデルがまだロードされていないという状況に陥ることがよくあります。ほとんどの場合、URL から取得したモデルの ID だけを持っています。たとえば、仮想市場アプリケーションの場合、ユーザーはその URL でアプリにアクセスします。

http://example.org/#/products/product0

myProductViewで、 を作成してProductModelその ID を設定し、product0次に I fetch(). プレースホルダーを使用して 1 回レンダリングし、フェッチが完了したら再レンダリングします。しかし、もっと良い方法があることを願っています。

何かをレンダリングする前にモデルが読み込まれるのを待つと、反応が鈍くなります。再レンダリングするとちらつきが発生し、「読み込み中... お待ちください」またはスピナーをどこにでも追加すると、ビュー テンプレートが非常に複雑になります (特に、モデルが存在しないか、ユーザーが表示する権限がないためにモデルのフェッチが失敗した場合)ページ)。

では、モデルがまだない場合にビューをレンダリングする適切な方法は何でしょうか?

ハッシュタグ ビューから離れて を使用する必要がありますpushStateか? サーバーはプッシュできますか? ぜひ聞きたいです。

すでに読み込まれたページからの読み込み:

ページに直接着陸するのではなく、既にページが読み込まれているときにできることはもっとあると思いProductます。

コレクションをレンダリングするなどして、アプリが製品ページへのリンクをレンダリングする場合、ProductOrder他にできることはありますか?

<ul id="product-order-list">
  <li>Ordered 5 days ago. Product 0 <a href="#/products/product0">(see details)</a></li>
  <li>Ordered 1 month ago. Product 1 <a href="#/products/product1">(see details)</a></li>
</ul>

このリンクから詳細ページへのパターンを処理する私の自然な方法は、次の行に沿って何かを行うルートを定義することです。

routes: {
  'products/:productid': 'showProduct'
  ...
}

showProduct: function (productid) {
  var model = new Product({_id: productid});
  var view = new ProductView({model: model});
  //just jam it in there -- for brevity
  $("#main").html(view.render().el);
}

fetch()次に、ビューのinitialize関数内で呼び出しthis.render()this.listenTo('change', ...)イベント リスナーから呼び出す傾向があります。これにより、複雑なrender()ケースが発生し、オブジェクトが表示されたり、ビューから消えたりします。たとえば、私のビュー テンプレートでは、Productユーザー コメント用に画面の一部を予約している可能性がありますが、製品にコメントが存在する/有効になっている場合に限られます。これは、モデルがサーバーから完全にフェッチされるまでは一般的にわかりません。

では、どこで/いつフェッチを行うのが最適でしょうか?

  1. ページが遷移する前にモデルをロードすると、ビュー コードは単純になりますが、ユーザーが認識できるほどの遅延が発生します。ユーザーはリスト内のアイテムをクリックし、モデルが返されるまで (ページが変更されることなく) 待つ必要があります。応答時間は重要であり、これについてのユーザビリティ調査は行っていませんが、ユーザーはリンクをクリックするとすぐにページが変更されることに慣れていると思います。

  2. ProductViewモデルをの内にロードし、モデル イベントをリッスンすると、2 回レンダリングする必要があります。1 回目は空のプレースホルダーを使用して (そうしないと、白いページを見つめる必要があるため)、1 回後ですinitializethis.model.fetch()読み込み中にエラーが発生した場合は、ビューを消去して (ちらつき/グリッチに見える)、何らかのエラーを表示する必要があります。

おそらくビュー間で再利用できる遷移読み込みページを含む、私が見ていない別のオプションはありますか? render()または、スピナー/読み込みインジケーターを表示するために常に最初の呼び出しを行うことをお勧めしますか?

編集:経由でロードcollection.fetch()

アイテムはすでにリストされているコレクション (リンクのリストをレンダリングするために使用されるコレクション) の一部であるため、リンクがクリックされる前に、collection.fetch(). コレクションが実際に のコレクションである場合Product、製品ビューをレンダリングするのは簡単です。

Collectionただし、リストの生成に使用される は ではない場合がありますProductCollectionProductOrderCollection製品 ID (または、製品 ID へのリンクを表示するのに十分な量の製品情報) への参照を単に持つ、または何か他のものである可能性があります。

モデルが大きい場合、 Producta を介してすべてを取得するcollection.fetch()ことも禁止される場合があります。Product製品リンクの 1 つがクリックされる可能性は低いです。

鶏か卵か?このcollection.fetch()アプローチは、製品ページに直接移動するユーザーの問題も実際には解決しません...この場合、ID (または製品ページにあるもの) だけからモデルを取得ProductViewする必要があるページをレンダリングする必要があります。 ProductURL)。

4

2 に答える 2

3

さて、私の意見では、これを修正できる方法はたくさんあります。私が考えたことをすべてリストアップします。うまくいけば、そのうちの 1 つがあなたとうまくいくか、少なくとも最適な解決策を見つけるきっかけになるでしょう。

私は T J の答えに完全に反対しているわけではありません。ウェブサイトの読み込み中にすべての製品で collection.fetch() を実行すると (ユーザーは通常、読み込みに時間がかかると予想します)、すべてのデータを取得し、そのデータを次のように渡すことができます。彼は述べた。彼が提案していることと私が通常行っていることとの唯一の違いは、通常、すべてのビューで app への参照があることです。たとえば、app.jsの初期化関数では、次のようにします。

initialize: function() {
    var options = {app: this}

    var views = {
        productsView: new ProductsView(options)
    };

    this.collections = {
        products: new Products()
    }

    // This session model is just a sandbox object that I use
    // to store information about the user's session. I would
    // typically store things like currentlySelectedProductId
    // or lastViewedProduct or things like that. Then, I just
    // hang it off the app for ease of access.
    this.models = {
        session: new Session()
    }
}

次に、productsView.js の初期化関数で次のようにします。

initialize: function(options) {
    this.app = options.app;

    this.views = {
        headerView: new HeaderView(options),
        productsListView: new ProductsListView(options),
        footerView: new FooterView(options)
    }; 
}

productsView.js の初期化で作成するサブビューは任意です。私はほとんどの場合、オプション オブジェクトをビューのサブビューにも渡し続けていることを実証しようとしていました。

これにより、トップレベルのビューであろうと深くネストされたサブビューであろうと、すべてのビューが許可され、すべてのビューが他のすべてのビューを認識し、すべてのビューがアプリケーション データへの参照を持ちます。

これら 2 つのコード サンプルでは、​​機能を可能な限り正確にスコープするという概念も紹介しています。すべてを行うビューを持とうとしないでください。各ビューが 1 つの特定の目的を持つように、機能を他のビューに渡します。これにより、ビューの再利用も促進されます。特に複雑なモーダル。

ここで、実際のトピックに戻ります。すべての製品を前もってロードする場合、どこでフェッチする必要がありますか? あなたが言ったように、ユーザーの前に空白のページを置いておきたくないからです。だから、私のアドバイスは彼らをだますことです。可能な限り多くのページを読み込み、データが必要な部分のみを読み込みからブロックします。そうすれば、実際に舞台裏で作業を行っているときに、ユーザーにはページが読み込まれているように見えます。ページが着実に読み込まれているとユーザーに思わせることができれば、ページの読み込みにイライラする可能性ははるかに低くなります。

したがって、productsView.js の初期化を参照すると、headerView と footerView をレンダリングすることができます。次に、productsListView のレンダリングでフェッチを行うことができます。

今、私はあなたが何を考えているか知っています。私は私の心を失ったのですか?render 関数でフェッチを行うと、productsViewList テンプレートを実際にレンダリングする行に到達する前に、呼び出しが戻る時間がありません。幸いなことに、それを回避する方法がいくつかあります。1 つの方法は、Promisesを使用することです。ただし、私が通常行う方法は、render 関数を独自のコールバックとして使用することです。披露させて。

render: function(everythingLoaded) {
    var _this = this;

    if(!!everythingLoaded) {
        this.$el.html(_.template(this.template(this)));
    }
    else {
        // load a spinner template here if you want a spinner

        this.app.collection.products.fetch()
            .done(function(data) {
                _this.render(true);
            })
            .fail(function(data) {
                console.warn('Error: ' + data.status);
            });
    }

    return this;
}

さて、このようにレンダーを構造化することで、データが完全にロードされるまで実際のテンプレートはロードされません。

ここではレンダー関数を使用していますが、どこでも使用する別の概念を紹介したいと思います。私はこれを postRender と呼んでいます。これは、テンプレートの読み込みが完了した後に配置されている DOM 要素に依存するコードを実行する関数です。単純な .html ページをコーディングしただけの場合、これは伝統的に$(document).ready(function() {});に入るコードです。. テンプレートに .html ファイルを使用していないことに注意してください。埋め込み JavaScript ファイル (.ejs)を使用しています. 続けて、postRender 関数は基本的に定型コードに追加した関数です。したがって、コード ベース内のビューに対して render を呼び出すときはいつでも、postRender をすぐにチェーンします。また、レンダーで行ったように、postRender をそれ自体のコールバックとして使用します。したがって、基本的に、前のコード例は私のコード ベースでは次のようになります。

render: function(everythingLoaded) {
    var _this = this;

    if(!!everythingLoaded) {
        this.$el.html(_.template(this.template(this)));
    }
    else {
        // load a spinner template here if you want a spinner

        this.app.collection.products.fetch()
            .done(function(data) {
                _this.render(true).postRender(true);
            })
            .fail(function(data) {
                console.warn('Error: ' + data.status);
            });
    }

    return this;
},

postRender: function(everythingLoaded) {
    if(!!everythingLoaded) {
        // do any DOM manipulation to the products list after 
        // it's loaded and rendered
    }
    else {
        // put code to start spinner
    }

    return this;
}

これらの関数をこのように連鎖させることで、それらが順番に実行されることが保証されます。

================================================== =======================

だから、それは問題に取り組むための1つの方法です。ただし、リクエストに時間がかかりすぎることを恐れて、必ずしもすべての製品を事前にロードする必要はないとおっしゃいました。


補足:呼び出しにかなりの時間がかかる可能性がある製品の呼び出しに関連する情報をすべて除外し、より大きな情報を別の要求にすることを検討する必要があります。主要な情報をすばやく取得でき、各製品に関連するサムネイルの読み込みに少し時間がかかる場合は、データの読み込みに時間がかかることをユーザーが許容するようになると思います。世界。それは私の意見です。


この問題を解決するもう 1 つの方法は、特定の製品ページに移動したいだけで、上記で概説したrender/postRender パターンを個々の productView に実装することです。ただし、productView.jsはおそらく次のようにする必要があることに注意してください。

initialize: function(options) {
    this.app = options.app;
    this.productId = options.productId;

    this.views = {
        headerView: new HeaderView(options),
        productsListView: new ProductsListView(options),
        footerView: new FooterView(options)
    }; 
}

render: function(everythingLoaded) {
    var _this = this;

    if(!!everythingLoaded) {
        this.$el.html(_.template(this.template(this)));
    }
    else {
        // load a spinner template here if you want a spinner

        this.app.collection.products.get(this.productId).fetch()
            .done(function(data) {
                _this.render(true).postRender(true);
            })
            .fail(function(data) {
                console.warn('Error: ' + data.status);
            });
    }

    return this;
},

postRender: function(everythingLoaded) {
    if(!!everythingLoaded) {
        // do any DOM manipulation to the product after it's 
        // loaded and rendered
    }
    else {
        // put code to start spinner
    }

    return this;
}

ここでの唯一の違いは、productId がオプション オブジェクトで初期化に渡され、それが引き出されてレンダリング関数の .fetch で使用されることです。

================================================== ======================= 結論として、これがお役に立てば幸いです。すべての質問に答えたかどうかはわかりませんが、かなりうまく答えられたと思います。長くなりすぎるので、ここで一旦停止し、これを消化して、質問があれば質問させてください。この投稿をさらに洗い流すには、少なくとも 1 回は更新する必要があると思います。

于 2015-12-02T18:18:08.030 に答える