Должен ли магистральный маршрутизатор или представление обрабатывать выборку данных и отображать состояние загрузки?

Во многих местах в моем приложении происходит следующая картина:

User clicks some link triggering navigation Data needs to be fetched to render the view UI design requires a "loading" spinner to be shown while data is fetched Once the data is fetched we show the rendered view

Я пробовал оба из следующих шаблонов реализации:

Router handles fetching

Router tells the container view to show the loading spinner Router loads any collections/models Router tells the container view to hide the loading spinner Router passes the collections/models to the view and renders it

View handles fetching

Router just creates and renders the view The view fetches the collections and models it needs When the view is first rendered, it just shows the loading spinner since the data is still loading When the data arrives, the models/collections fire events and the view is bound to those so it re-renders itself, thus hiding the loading spinner and showing the full view

Мне не нравится # 1, так как маршрутизатор становится гигантским шаром логики выборки Model / Collection и, кажется, несет слишком большую ответственность. # 2 кажется лучшим распределением обязанностей (маршрутизатор просто решает, какое представление показывать, просмотр выясняет, какие данные ему нужно получить), но это действительно делает представление более сложным, так как теперь оно является состоящим.

Что думает сообщество StackOverflow? 1, 2 или что-то еще?

Ответы на вопрос(2)

Решение Вопроса

но мы рассматривали его ранее сегодня, поэтому на случай, если кто-нибудь еще столкнется с ним:

Для меня, я действительно вижу 2 отдельных вопроса:

Where should the mechanics of data fetching and the resulting view rendering happen, in the router or a view? Should views expect already resolved models, or should they respond to models that may still be loading?

Немного о том, как мы справляемся с этим, с некоторыми личными предпочтениями:

Neither, although I'd lean closer to the router.  Routers should handle routing, views should handle viewing, and something else should handle the mechanics and workflow of Model/Collection fetching logic.  We call that something else a Controller, which the Router basically delegates to. As Yuri alludes to, 'sometimes' is a reality.  I think this is probably a case by case decision, but should ultimately be a contract between a Controller and View, rather than between the Router/View.

Мне нравятся маркеры Юрия с парой предостережений (маркеры с отступом):

The router only knows where to send the user The outer view only knows what the user should be viewing (given its data)
Assuming the outer view is specific to the inner view's use case and is 'owned' by another view (for clean up) Otherwise for generic containers(like rendering into a 'main' location), we've found it useful to have a component that manages the views for a certain 'section' on the page - we call it a Renderer The inner views only know how to show only their little piece of it all (and can be used elsewhere) and The render function always shows the right thing as of right now.
In the case of a generic container, it'd ultimately be the responsibility of the Renderer

Основная причина для Renderer заключается в обработке вещей, связанных с этим разделом, таких как очистка существующих представлений, чтобы избежать призрачных представлений, прокрутка до верха при рендеринге (это делает наш MainContentRenderer) или показ спиннера в этом случае.

Пример того, как это может выглядеть, для psuedo-code-ish:

a generic content target 'main' (if it's use case specific, may be better off with a ComponentView as per Yuri's example, depending on your view lifecycle management strategy) a model we have to fetch and wait on a view that accepts an already loaded model

Router:

routes: {
    "profile": "showProfile"
},

showProfile: function() {
    return new ProfileController().showProfile();
}

ProfileController:

showProfile: function() {
    //simple case
    var model = new Model();
    var deferredView = model.fetch.then(function() {
        return new View(model);
    };
    MainContentRenderer.renderDeferred(deferredView);
}

MainContentRenderer:

var currentView;

renderDeferred: function(deferredView) {
    showSpinner();
    deferredView.then(function(view) {
        this.closeSpinner();
        this.closeCurrentView();
        this.render(view);
    }
},

render: function(view) {
    currentView = view;
    $('#main-content').html(view.render().el);
}

closeCurrentView: function() {
    if (currentView and currentView.close()) {
        currentView.close();
    }
}

Представление контроллера также имеет дополнительное преимущество тестируемости. Например, у нас есть сложные правила для выполнения поиска вокруг управления URL-адресами, выбора между представлением результатов и новым представлением поиска и выбора между кэшированным «последним». результат поиска и выполнение нового поиска. У нас есть тесты Jasmine для контроллеров, чтобы убедиться, что вся эта логика потока верна. Это также обеспечивает изолированное место для управления этими правилами.

 19 мар. 2013 г., 16:44
У нас похожая сборка с контроллерами после роутера. Только то, что контроллер создает модель и запускает выборку и непосредственно создает экземпляр для нее. Как только модель загружается, рендер все равно запускается из-за события change.
 12 мая 2013 г., 13:48
Это действительно полезно, спасибо, что поделились. Я боролся с этим в течение последних нескольких недель, пытаясь найти лучший способ сделать что-то - мне нравится идея разделиться на Контроллеры и Рендереры. Я чувствую запах плагина Backbone!

контейнером, представлением загрузки и представлением содержимого. То есть контейнер создается маршрутизатором, и во время каждого рендеринга он просматривает то, что у него есть в наличии для отображения, иногда предоставляемое маршрутизатором, иногда само по себе, и решает, какие представления создавать. Упрощенный, надуманный пример:

ContainerView = Backbone.View.extend({

  initialize: function (options) {
    options.data.bind("reset", this.render, this);
  },

  render: function () {
    var view;

    // If the loading view is only shown once, e.g., splashing, then isReady()
    // may be better here.
    if (this.options.data.isLoading()) {
      view = LoadingView;
    } else {
      view = DataView;
    }

    this.$("div.content").html(new view().render().el);
  }

});

Мне нравится этот подход, потому что:

The router only knows where to send the user; The outer view only knows what the user should be viewing (given its data); The inner views only know how to show only their little piece of it all (and can be used elsewhere); and The render function always shows the right thing as of right now.

Clarification: В данном случае цель представления состоит в том, чтобы понять, как лучше всего показать пользователю то, что должно быть показано. В этом случае часть загружаемых данных лучше всего показывать в представлении загрузки, тогда как готовые данные лучше всего отображать в представлении данных. Большинство реальных представлений фактически составляют их отображение со многими другими представлениями, например, в зависимости от авторизации пользователя различными контейнерами действий.

 31 мая 2012 г., 03:30
@Peter Lyons Они были бы «да» (то есть isLoading, isReady,), поскольку то, как модель или коллекция указывает свое состояние, довольно специфично для проекта. Данные могут поступать от создателя представления или от самого представления, или от обоих.
 Peter Lyons30 мая 2012 г., 19:54
Так и естьisLoading а такжеisReady функции вы написали? Предположительно, вы передаетеoptions.data вплоть до просмотра данных, да? Я не уверен, что мне нравится вид контейнера, заботящийся о загрузке или нет.

Ваш ответ на вопрос