17

在我的应用程序的许多地方,都会发生以下模式:

  • 用户点击一些链接触发导航
  • 需要获取数据以呈现视图
  • UI 设计需要在获取数据时显示“加载”微调器
  • 获取数据后,我们将显示渲染视图

我尝试了以下两种实现模式:

  1. 路由器处理获取

    • 路由器告诉容器视图显示加载微调器
    • 路由器加载任何集合/模型
    • 路由器告诉容器视图隐藏加载微调器
    • 路由器将集合/模型传递给视图并呈现它
  2. 查看句柄获取

    • 路由器只是创建和渲染视图
    • 视图获取它需要的集合和模型
    • 首次渲染视图时,它只显示加载微调器,因为数据仍在加载
    • 当数据到达时,模型/集合触发事件并且视图绑定到这些事件,因此它重新渲染自身,从而隐藏加载微调器并显示完整视图

我不喜欢#1,因为路由器变成了一个巨大的模型/集合获取逻辑球,并且似乎有太多的责任。#2 似乎是更好的职责分配(路由器只是决定显示哪个视图,视图确定它需要获取哪些数据),但它确实使视图呈现有点棘手,因为它现在是有状态的。

StackOverflow 社区是怎么想的?1、2还是别的什么?

4

2 回答 2

23

这篇文章很老了,但我们今天早些时候正在审查它,所以以防其他人遇到它:

对我来说,我真的看到了两个不同的问题:

  1. 数据获取机制和结果视图渲染应该发生在路由器还是视图中?
  2. 视图应该期待已经解析的模型,还是应该响应可能仍在加载的模型?

我们处理它的方式与一些个人喜好混合在一起:

  1. 两者都没有,尽管我会更靠近路由器。路由器应该处理路由,视图应该处理查看,而其他东西应该处理模型/集合获取逻辑的机制和工作流程。我们称其为控制器,路由器基本上委托给它。
  2. 正如尤里所暗示的,“有时”是现实。我认为这可能是个案决定,但最终应该是控制器和视图之间的合同,而不是路由器/视图之间的合同。

我喜欢 Yuri 的要点,有几个注意事项(缩进的项目符号):

  • 路由器只知道将用户发送到哪里
  • 外部视图只知道用户应该查看什么(给定它的数据)
    • 假设外部视图特定于内部视图的用例并且由另一个视图“拥有”(用于清理)
    • 否则对于通用容器(例如渲染到“主”位置),我们发现拥有一个管理页面上某个“部分”的视图的组件很有用 - 我们称之为渲染器
  • 内部视图只知道如何只显示它们的一小部分(并且可以在其他地方使用)
  • 并且渲染功能始终显示正确的内容。
    • 对于通用容器,最终由渲染器负责

Renderer 的主要原因是处理与该部分相关的事情,例如清理现有视图以避免重影视图,在渲染时滚动到顶部(我们的 MainContentRenderer 会这样做),或者在这种情况下显示一个微调器。

一个可能看起来像的伪代码示例,用于:

  • 通用内容目标“主要”(如果它是特定于用例的,根据 Yuri 的示例,使用 ComponentView 可能会更好,具体取决于您的视图生命周期管理策略)
  • 我们必须获取并等待的模型
  • 接受已加载模型的视图

路由器:

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

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

配置文件控制器:

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

主要内容渲染器:

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 测试,以验证所有流逻辑是否正确。它还提供了一个隔离的地方来管理这些规则。

于 2013-02-28T05:18:07.270 回答
7

我倾向于使用带有三个视图的第二个选项,即容器、加载视图和内容视图。也就是说,容器由路由器实例化,并且在每次渲染期间,它会查看手头要显示的内容(有时由路由器提供,有时由其自身提供)并决定要实例化哪些视图。一个简单的,人为的例子:

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

});

我喜欢这种方法,因为:

  • 路由器只知道将用户发送到哪里;
  • 外部视图只知道用户应该查看什么(给定它的数据);
  • 内部视图只知道如何只显示它们的一小部分(并且可以在其他地方使用);和
  • 渲染功能始终显示正确的内容。

澄清: 在这种情况下,视图的目的是了解必须显示的内容应该如何最好地显示给用户。在这种情况下,一些仍在加载的数据最好用加载视图显示,而准备好的数据最好用数据视图显示。大多数真实视图实际上是用更多视图组成它们的显示,例如,根据用户授权不同的动作容器。

于 2012-05-30T01:01:11.113 回答