这是一个很好的问题。Backbone之所以伟大,是因为它没有做出任何假设,但这也意味着你必须自己实现类似的东西。在查看自己的东西后,我发现我有点使用了场景1和场景2的混合。我认为不存在第四种神奇的情况,因为简单地说,你在场景1和2中所做的一切都必须完成。
我认为最容易解释我如何处理它的方法是举个例子。假设我有这个简单页面,分成了指定的视图:
![Page Breakdown](https://istack.dev59.com/k2QMj.webp)
假设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非常简单明了。
第二步,渲染是为了确保任何子视图依赖的元素在我尝试分配它们之前已经存在。通过这样做,我知道所有子
事件
将被正确设置,并且我可以重新渲染它们的块,而不必担心重新委派任何内容。我实际上没有在这里
渲染
任何子视图,我允许它们在其自己的
初始化
中执行。
第三步和第四步实际上是在创建子视图时同时处理的,因为我在创建子视图时传递了
el
。我喜欢在这里传递一个元素,因为我觉得父级应该确定子级在其自己的视图中可以放置其内容的位置。
对于渲染,我尽量让
Parent
视图保持简单。我希望
render
函数仅仅渲染父视图。没有委派事件,没有渲染子视图,什么也没有。只有一个简单的渲染。
有时候这并不总是有效的。例如在上面的例子中,当模型中的名称更改时,
#name
元素需要更新。然而,这个区块是
ParentView
模板的一部分,而不是由专门的
Child
视图处理,因此我会绕过它。我将创建某种类型的
subRender
函数,仅替换
#name
元素的内容,而无需删除整个
#parent
元素。这可能看起来像是一个 hack,但我真的发现它比担心重新渲染整个 DOM 并重新连接元素之类的事情要好。如果我真的想让它变得干净,我会创建一个新的
Child
视图(类似于
InfoView
),来处理
#name
区块。
现在对于
Child
视图,
initialization
与
Parent
视图非常相似,只是不需要创建任何进一步的
Child
视图。所以:
- 初始化我的视图
- 设置绑定以监听我关心的模型的任何更改
- 渲染我的视图
Child
视图的渲染也非常简单,只需渲染并设置我的 el
的内容即可。同样,不需要处理委托或其他类似的事情。
这是一个示例代码,展示了我的 ParentView
可能是什么样子:
var ParentView = Backbone.View.extend({
el: "#parent",
initialize: function() {
this.model.bind("change:first_name", this.subRender, this);
this.model.bind("change:last_name", this.subRender, this);
this.render();
this.infoView = new InfoView({el: "#info", model: this.model});
this.phoneListView = new PhoneListView({el: "#phone_numbers", model: this.model});
},
render: function() {
this.$el.html(this.template());
this.subRender();
},
subRender: function() {
$("#name").html("Person: " + this.model.first_name + " " + this.model.last_name);
}
});
你可以在这里看到我对于
subRender
的实现。通过将更改绑定到
subRender
而不是
render
,我无需担心摧毁和重建整个块。
下面是
InfoView
块的示例代码:
var InfoView = Backbone.View.extend({
initialize: function() {
this.model.bind("change", this.render, this);
this.render();
},
render: function() {
this.$el.html(this.template());
}
});
绑定是重要的部分。通过绑定到我的模型,我永远不必担心手动调用
render
函数。如果模型更改,此块将重新呈现自身,而不影响任何其他视图。
PhoneListView
与
ParentView
类似,只需在
initialization
和
render
函数中增加一些逻辑以处理集合。如何处理集合真的取决于您,但至少需要监听集合事件并决定如何呈现(追加/删除或仅重新呈现整个块)。我个人喜欢追加新视图并删除旧视图,而不是重新呈现整个视图。
PhoneView
与
InfoView
几乎完全相同,只需要关注它关心的模型更改。
希望这有所帮助,请让我知道是否有任何困惑或不够详细。
setElement()
之后调用delegateEvents()
?根据文档:“...并将视图的委托事件从旧元素移动到新元素”,setElement
方法本身应该处理事件重新委派。 - rdamborsky