我有三种不同的方法来初始化和渲染视图及其子视图,每种方法都有不同的问题。我很想知道是否有更好的方法可以解决所有问题:
在父级的初始化函数中初始化子级。这样,并非所有内容都卡在渲染中,从而减少了渲染阻塞。
initialize : function () { //parent init stuff this.child = new Child(); }, render : function () { this.$el.html(this.template()); this.child.render().appendTo(this.$('.container-placeholder'); }
问题:
最大的问题是第二次在父节点上调用渲染将删除所有子事件绑定。(这是因为 jQuery 的$.html()工作方式。)这可以通过调用this.child.delegateEvents().render().appendTo(this.$el);来缓解,但是第一种,也是最常见的情况,你做了更多不必要的工作。
$.html()
this.child.delegateEvents().render().appendTo(this.$el);
通过添加子元素,您可以强制渲染函数了解父 DOM 结构,以便获得所需的排序。这意味着更改模板可能需要更新视图的渲染功能。
初始化父级中的子initialize()级,但不是追加,而是用于将setElement().delegateEvents()子级设置为父级模板中的元素。
initialize()
setElement().delegateEvents()
initialize : function () { //parent init stuff this.child = new Child(); }, render : function () { this.$el.html(this.template()); this.child.setElement(this.$('.placeholder-element')).delegateEvents().render(); }
delegateEvents()
而是在父母的render()方法中初始化孩子。
render()
initialize : function () { //parent init stuff }, render : function () { this.$el.html(this.template()); this.child = new Child(); this.child.appendTo($.('.container-placeholder').render(); }
这意味着渲染函数现在也必须与所有初始化逻辑绑定在一起。
如果我编辑其中一个子视图的状态,然后在父视图上调用渲染,则会创建一个全新的子视图,并且其所有当前状态都将丢失。这似乎也可能因内存泄漏而变得冒险。
真的很想得到你的家伙对此的看法。你会使用哪种场景?还是有第四种神奇的方法可以解决所有这些问题?
您是否曾经跟踪过视图的渲染状态?说renderedBefore国旗?看起来真的很鸡肋。
renderedBefore
这是一个很好的问题。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 如何与图表匹配非常明显。
拥有ParentView2 个子视图,InfoView以及PhoneListView一些额外的 div,其中一个#name需要在某个时候设置。 PhoneListView拥有自己的子视图,一个PhoneView条目数组。
ParentView
InfoView
PhoneListView
#name
PhoneView
那么关于你的实际问题。我根据视图类型以不同方式处理初始化和渲染。我把我的观点分为两种,Parent观点和Child观点。
Parent
Child
它们之间的区别很简单,Parent视图拥有子视图,而Child视图则没有。所以在我的例子中,ParentViewandPhoneListView是Parent视图,而InfoView条目PhoneView是Child视图。
就像我之前提到的,这两个类别之间的最大区别在于它们何时被允许渲染。在一个完美的世界中,我希望Parent视图只渲染一次。当模型更改时,由他们的子视图处理任何重新渲染。Child另一方面,我允许在需要时重新渲染视图,因为它们没有任何其他依赖于它们的视图。
更详细地说,对于Parent视图,我喜欢我的initialize函数做一些事情:
initialize
#info
第 1 步很容易解释。
第 2 步(渲染)已完成,以便子视图所依赖的任何元素在我尝试分配它们之前已经存在。通过这样做,我知道所有孩子events都将被正确设置,并且我可以根据需要多次重新渲染他们的块,而不必担心必须重新委托任何东西。我render在这里实际上没有任何子视图,我允许他们在自己的initialization.
events
render
initialization
第 3 步和第 4 步实际上是在我el在创建子视图时传入的同时处理的。我喜欢在这里传递一个元素,因为我觉得父母应该确定允许孩子在自己的视图中放置其内容的位置。
el
对于渲染,我尽量让Parent视图保持简单。我希望该render函数只做渲染父视图。没有事件委托,没有子视图的渲染,什么都没有。只是一个简单的渲染。
有时这并不总是有效。例如,在我上面的示例中,#name只要模型中的名称发生更改,就需要更新元素。但是,这个块是ParentView模板的一部分,不是由专用Child视图处理的,所以我解决了这个问题。我将创建某种 只 替换元素内容的subRender函数,而不必丢弃整个元素。这可能看起来像一个 hack,但我真的发现它比不必担心重新渲染整个 DOM 和重新附加元素等效果更好。如果我真的想让它变得干净,我会创建一个新视图(类似于)来处理该块。 __#name``#parent``Child``InfoView``#name
subRender
#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,只听它关心的模型变化。
希望这对您有所帮助,如果有任何令人困惑或不够详细的地方,请告诉我。