好吧,所以在我看来,有很多方法可以解决这个问题。我将列出我所想到的所有内容,希望有人能与您合作,或者至少会激发您找到最佳解决方案。
我并不完全反对 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()
}
}
然后在我的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 的初始化中创建的子视图是任意的。我主要是想证明我继续将该选项对象传递给视图的子视图。
这样做是允许每个视图,无论是顶级视图还是深度嵌套的子视图,每个视图都知道其他视图,并且每个视图都可以引用应用程序数据。
这两个代码示例还介绍了尽可能精确地确定功能范围的概念。不要试图拥有无所不能的观点。将功能传递给其他视图,以便每个视图都有一个特定的用途。这也将促进视图的重用。尤其是复杂的模态。
现在回到手头的实际主题。如果您要继续并预先加载所有产品,您应该在哪里获取它们?因为就像您说的那样,您不希望在用户面前出现空白页面。所以,我的建议是欺骗他们。尽可能多地加载页面,并且只阻止需要加载数据的部分。这样对用户来说,页面看起来像是在加载,而您实际上是在幕后工作。如果您可以诱使用户认为页面正在稳定加载,那么他们就不太可能对页面加载感到不耐烦。
因此,引用 productsView.js 中的初始化,您可以继续让 headerView 和 footerView 呈现。然后,您可以在 productsListView 的渲染中进行获取。
现在,我知道你在想什么。我是不是失去理智了。如果您在渲染函数中进行 fetch,那么在我们点击实际渲染 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;
}
现在,通过以这种方式构建我们的渲染,实际模板在数据完全加载之前不会加载。
虽然我们在这里有一个渲染功能,但我想介绍另一个我在任何地方都使用的概念。我称之为 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;
}
通过像这样链接这些函数,我们保证它们将按顺序运行。
==================================================== ========================
所以,这是解决问题的一种方法。但是,您提到您不必预先加载所有产品,因为担心请求可能需要太长时间。
旁注:您真的应该考虑取出与产品通话相关的任何信息,这些信息可能会导致通话花费大量时间,并将较大的信息作为单独的请求。我有一种感觉,如果您可以非常快速地让他们获得核心信息并且如果与每个产品相关的缩略图需要更长的时间来加载它不应该那么结束,那么用户会更宽容地加载数据需要一段时间世界。这只是我的意见。
解决此问题的另一种方法是,如果您只想转到特定的产品页面,那么只需在单个 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 次更新才能进一步清除它。