这是一个很好的问题。Backbone 很棒,因为它缺乏假设,但这确实意味着您必须(决定如何)自己实现这样的事情。在查看我自己的东西后,我发现我(有点)混合使用了场景 1 和场景 2。我认为不存在第四个神奇场景,因为,简单地说,你在场景 1 和 2 中所做的一切都必须是完毕。
我认为用一个例子来解释我喜欢如何处理它是最容易的。假设我将这个简单的页面分解为指定的视图:
假设 HTML 在渲染后是这样的:
<div id="parent">
<div id="name">Person: Kevin Peel</div>
<div id="info">
First name: <span class="first_name">Kevin</span><br />
Last name: <span class="last_name">Peel</span><br />
</div>
<div>Phone Numbers:</div>
<div id="phone_numbers">
<div>#1: 123-456-7890</div>
<div>#2: 456-789-0123</div>
</div>
</div>
希望 HTML 与图表的匹配方式非常明显。
在ParentView
拥有2个视图,InfoView
并且PhoneListView
还有一些额外的div,其中之一,#name
,需要在某些点进行设置。 PhoneListView
拥有自己的子视图,一个PhoneView
条目数组。
所以说到你的实际问题。我根据视图类型以不同的方式处理初始化和渲染。我把我的观点分成两种类型,Parent
观点和Child
观点。
它们之间的区别很简单,Parent
视图持有子视图而Child
视图不持有。所以在我的例子,ParentView
并且PhoneListView
是Parent
观点,而InfoView
与PhoneView
条目Child
意见。
就像我之前提到的,这两个类别之间最大的区别在于它们何时被允许渲染。在一个完美的世界中,我希望Parent
视图只渲染一次。当模型更改时,由他们的子视图来处理任何重新渲染。Child
另一方面,我允许在他们需要的任何时候重新渲染,因为他们没有任何其他视图依赖它们。
更详细地说,对于Parent
视图,我喜欢我的initialize
函数做一些事情:
- 初始化我自己的视图
- 呈现我自己的观点
- 创建并初始化任何子视图。
- 在我的视图中为每个子视图分配一个元素(例如,
InfoView
将被分配#info
)。
第 1 步是不言自明的。
第 2 步,渲染已经完成,因此在我尝试分配它们之前,子视图所依赖的任何元素都已经存在。通过这样做,我知道所有孩子events
都将被正确设置,并且我可以根据需要多次重新渲染他们的块,而不必担心必须重新委派任何东西。我实际上render
在这里没有任何孩子的观点,我允许他们在自己的initialization
.
第 3 步和第 4 步实际上是在我el
创建子视图时传入的同时处理的。我喜欢在这里传递一个元素,因为我觉得父级应该确定允许子级在自己的视图中放置其内容的位置。
对于渲染,我尽量让Parent
视图非常简单。我希望该render
函数只渲染父视图。没有事件委托,没有子视图的呈现,什么都没有。只是一个简单的渲染。
有时这并不总是有效。例如,在我上面的示例中,#name
只要模型中的名称发生变化,元素就需要更新。但是,这个块是ParentView
模板的一部分,不是由专用Child
视图处理的,所以我解决了这个问题。我将创建某种只替换元素内容的subRender
函数,而不必删除整个元素。这可能看起来像一个 hack,但我真的发现它比不必担心重新渲染整个 DOM 和重新附加元素等要好得多。如果我真的想让它干净,我会创建一个新的视图(类似于)来处理块。#name
#parent
Child
InfoView
#name
现在对于Child
视图,initialization
它与Parent
视图非常相似,只是没有创建任何进一步的Child
视图。所以:
- 初始化我的视图
- 设置绑定监听我关心的模型的任何更改
- 呈现我的观点
Child
视图渲染也很简单,只需要渲染和设置我的el
. 同样,不要搞乱委托或类似的事情。
下面是一些示例代码,说明我的ParentView
可能是什么样子:
var ParentView = Backbone.View.extend({
el: "#parent",
initialize: function() {
// Step 1, (init) I want to know anytime the name changes
this.model.bind("change:first_name", this.subRender, this);
this.model.bind("change:last_name", this.subRender, this);
// Step 2, render my own view
this.render();
// Step 3/4, create the children and assign elements
this.infoView = new InfoView({el: "#info", model: this.model});
this.phoneListView = new PhoneListView({el: "#phone_numbers", model: this.model});
},
render: function() {
// Render my template
this.$el.html(this.template());
// Render the name
this.subRender();
},
subRender: function() {
// Set our name block and only our name block
$("#name").html("Person: " + this.model.first_name + " " + this.model.last_name);
}
});
你可以在subRender
这里看到我的实现。通过将更改绑定到subRender
而不是render
,我不必担心炸掉和重建整个块。
下面是InfoView
块的示例代码:
var InfoView = Backbone.View.extend({
initialize: function() {
// I want to re-render on changes
this.model.bind("change", this.render, this);
// Render
this.render();
},
render: function() {
// Just render my template
this.$el.html(this.template());
}
});
绑定是这里的重要部分。通过绑定到我的模型,我永远不必担心手动调用render
自己。如果模型发生变化,此块将重新渲染自身,而不会影响任何其他视图。
该PhoneListView
会类似ParentView
,你只需要在你的都多一点的逻辑initialization
和render
函数来处理集合。您如何处理集合实际上取决于您,但您至少需要监听集合事件并决定如何呈现(追加/删除,或只是重新呈现整个块)。我个人喜欢添加新视图并删除旧视图,而不是重新渲染整个视图。
该PhoneView
会是几乎相同的InfoView
,只是听着它关心模型的变化。
希望这对您有所帮助,如果有任何令人困惑或不够详细的内容,请告诉我。