在哪一点上获取视图所依赖的Backbone模型?

ini*_*_js 5 javascript client-side-scripting backbone.js

我似乎经常在我渲染视图的情况下结束,但该视图所依赖的模型尚未加载.大多数情况下,我只从URL中获取模型的ID,例如对于假设的市场应用程序,用户使用该URL登陆应用程序:

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

在我ProductView,我创建一个ProductModel并设置其ID,product0然后我fetch().我用占位符渲染一次,当获取完成时,我重新渲染.但我希望有更好的方法.

在呈现任何内容之前等待模型加载感觉没有响应.重新渲染的原因闪烁,并添加"载入中...请稍候"或者处处纺纱使视图模板非常复杂的(尤其是如果模型获取,因为模型不存在故障,或者用户无权查看这页纸).

那么,当你还没有模型时,渲染视图的正确方法是什么?

我是否需要远离hashtag-views并使用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>
Run Code Online (Sandbox Code Playgroud)

我处理这种链接到细节页面模式的自然方式是定义一条沿着这些方向做某事的路线:

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);
}
Run Code Online (Sandbox Code Playgroud)

然后我倾向于fetch()在视图的initialize函数内部调用,并this.render()this.listenTo('change', ...)事件监听器调用.这导致复杂的render()情况,并且对象在视图中出现和消失.例如,我的一个视图模板Product可能保留一些屏幕房地产用户评论,但是当且仅当意见存在/在产品中启用-和之前的模式是从服务器获取完全一般不知道.

现在,哪里/何时最好进行获取?

  1. 如果我在页面转换之前加载模型,则会导致直观的视图代码,但会引入用户可察觉的延迟.用户将单击列表中的项目,并且必须等待(不更改页面)才能返回模型.响应时间非常重要,我还没有对此进行可用性研究,但我认为用户习惯于在单击链接后立即更改页面.

  2. 如果我加载内部模型ProductViewinitialize,与this.model.fetch()和监听模型事件,我不得不两次渲染, -一旦与空占位符之前(因为否则的话,你必须在一个白色的页面盯),并经过一次.如果在加载过程中发生错误,那么我必须擦除视图(显示为flickery/glitchy)并显示一些错误.

是否有其他选项我没有看到,可能涉及可以在视图之间重用的过渡加载页面?或者总是第一次打电话来render()显示一些微调器/加载指示器是好的做法?

编辑:通过加载 collection.fetch()

有人可能会建议,因为这些项已经是列出的集合的一部分(用于呈现链接列表的集合),所以可以在单击链接之前获取它们collection.fetch().如果集合确实是一个集合Product,那么渲染产品视图会很容易.

然而,Collection用于生成列表的用户可能不是ProductCollection.它可能是一个ProductOrderCollection或其他只是引用产品ID(或一些足够数量的产品信息来呈现它的链接).

如果模型很大,尤其是Product通过a 获取collection.fetch()也可能是禁止的Product.在点击其中一个产品链接的可能性.

鸡还是鸡蛋?该collection.fetch()方法也没有真正解决直接导航到产品页面的用户的问题......在这种情况下,我们仍然需要渲染一个ProductView页面,该页面需要Product从id(或产品页面中的任何内容)中获取模型URL).

Cli*_*unn 3

好吧,我认为有很多方法可以解决这个问题。我将列出我想到的所有内容,希望其中之一能够与您合作,或者至少它会激励您找到最佳解决方案。

我并不完全反对T J 的回答。如果您在网站加载时对所有产品执行 collection.fetch() (用户通常期望涉及一些加载时间),那么您就拥有了所有数据,并且可以像这样传递该数据他提到。他的建议和我通常所做的唯一区别是我通常在所有视图中都会引用应用程序。因此,例如在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()
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在我的productsView.js 初始化函数中我会这样做:

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

    this.views = {
        headerView: new HeaderView(options),
        productsListView: new ProductsListView(options),
        footerView: new FooterView(options)
    }; 
}
Run Code Online (Sandbox Code Playgroud)

我在 productsView.js 的初始化中创建的子视图是任意的。我主要只是想证明我也继续将该选项对象传递给视图的子视图。

这样做的目的是允许每个视图,无论是顶级视图还是深层嵌套的子视图,每个视图都知道每个其他视图,并且每个视图都可以引用应用程序数据。

这两个代码示例还介绍了尽可能精确地确定功能范围的概念。不要试图拥有一个包揽一切的视图。将功能传递给其他视图,以便每个视图都有一个特定的用途。这也将促进视图的重用。尤其是复杂情态动词。

现在回到当前的实际主题。如果您要继续预先加载所有产品,您应该在哪里获取它们?因为就像您所说的,您不希望在用户面前出现一个空白页面。所以,我的建议是欺骗他们。尽可能多地加载页面,并且仅阻止加载需要数据的部分。这样,对于用户来说,页面看起来就像正在加载,而您实际上是在幕后工作。如果您可以欺骗用户,让他们认为页面正在稳定加载,那么他们就不太可能对页面加载感到不耐烦。

因此,引用 productsView.js 中的初始化,您可以继续让 headerView 和 footerView 呈现。然后,您可以在 productsListView 的渲染中进行提取。

现在,我知道你在想什么。我是不是失去理智了。如果您在渲染函数中进行提取,那么在我们到达实际渲染 productsViewList 模板的行之前,调用将没有时间返回。幸运的是,有几种方法可以解决这个问题。一种方法是使用Promises。但是,我通常的做法是仅使用渲染函数作为其自己的回调。我来给你展示。

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;
}
Run Code Online (Sandbox Code Playgroud)

现在,通过这种方式构建我们的渲染,实际模板在数据完全加载之前不会加载。

虽然我们这里有一个渲染函数,但我想介绍另一个我在任何地方都使用的概念。我称之为后渲染。在这个函数中,我可以在模板加载完成后执行任何依赖于 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;
}
Run Code Online (Sandbox Code Playgroud)

通过像这样链接这些函数,我们保证它们将按顺序运行。

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

所以,这是解决问题的一种方法。但是,您提到您不必预先加载所有产品,因为担心请求可能需要太长时间。


旁注:您确实应该考虑删除与产品调用相关的任何信息,这些信息可能会导致调用花费大量时间,并将较大的信息作为单独的请求。我有一种感觉,如果您能够非常快地为用户提供核心信息,并且如果与每个产品相关的缩略图需要更长的时间来加载,那么用户将更宽容地加载数据需要一段时间,那么就不应该结束世界。这只是我的意见。


解决此问题的另一种方法是,如果您只想转到特定的产品页面,则只需在各个 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;
}
Run Code Online (Sandbox Code Playgroud)

这里唯一的区别是,productId 在选项对象中传递给初始化,然后在渲染函数的 .fetch 中被拉出并使用。

=================================================== ======================= 总之,我希望这会有所帮助。我不确定我是否已经回答了你们所有的问题,但我认为我已经很好地解决了这些问题。由于篇幅太长,我将暂时停在此处,让您消化一下并提出您的任何问题。我想我可能需要对这篇文章至少进行 1 次更新才能进一步清除它。