如何处理Backbone.js中的初始化和渲染子视图?
初始化和渲染视图及其子视图有三种不同的方法,每种方法都有不同的问题。 我很想知道是否有更好的解决所有问题的方法:
情景一:
初始化父项初始化函数中的子项。 这样,并不是所有东西都被卡在渲染中,所以在渲染时阻塞较less。
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);
来缓解this.child.delegateEvents().render().appendTo(this.$el);
相反,但是第一个,也是最常见的情况是,你不必要地做更多的工作。 -
通过追加孩子,你可以强制渲染function掌握父母DOM结构的知识,以便得到你想要的顺序。 这意味着更改模板可能需要更新视图的渲染function。
情景二:
初始化父对象的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()
方法中初始化子级。
initialize : function () { //parent init stuff }, render : function () { this.$el.html(this.template()); this.child = new Child(); this.child.appendTo($.('.container-placeholder').render(); }
问题:
-
这意味着渲染函数现在必须与所有的初始化逻辑捆绑在一起。
-
如果我编辑其中一个子视图的状态,然后调用父对象的渲染,那么将会创build一个全新的子对象,并且所有的当前状态都将丢失。 这也似乎可以得到内存泄漏的冒险。
真的好奇,让你的家伙承担这一点。 你会使用哪种场景? 还是有第四个神奇的解决所有这些问题?
你有没有跟踪视图的渲染状态? 说一个renderedBefore
标志? 似乎真的很难受。
这是一个很好的问题。 骨干是伟大的,因为它缺乏假设,但这确实意味着你必须(决定如何)自己实现这样的事情。 看完我自己的东西后,我发现我(种)混合使用场景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
包含两个子视图, InfoView
和PhoneListView
以及一些额外的div,其中之一需要在某个点设置#name
。 PhoneListView
保存其自己的子视图,一个PhoneView
条目数组。
所以到你的实际问题。 我基于视图types处理初始化和渲染。 我把我的看法分成两种types, Parent
视图和Child
视图。
他们之间的区别很简单, Parent
视图保持子视图,而Child
视图不能。 所以在我的例子中, ParentView
和PhoneListView
是Parent
视图,而InfoView
和PhoneView
则是Child
视图。
就像我之前提到的那样,这两个类别之间最大的区别是它们被允许渲染。 在一个完美的世界中,我希望Parent
视图只能渲染一次。 当模型发生变化时,由子视图来处理任何重新渲染。 另一方面, Child
意见,我允许任何时候重新渲染,因为他们没有任何其他意见依赖于他们。
再详细一点,对于Parent
视图,我喜欢我的initialize
函数来做一些事情:
- 初始化我自己的观点
- 呈现我自己的观点
- 创build并初始化任何子视图。
- 为我的视图中的每个子视图分配一个元素(例如,
InfoView
将被分配#info
)。
第一步相当自我解释。
第2步,渲染完成后,我试图分配它们之前,任何子视图依赖的元素已经存在。 通过这样做,我知道所有的孩子events
都将被正确设置,并且我可以根据需要多次重新渲染他们的块,而不必担心需要重新分配任何东西。 我实际上并没有在这里render
任何的子视图,我允许他们在自己的initialization
这样做。
步骤3和步骤4实际上是在创build子视图的同时处理的。 我喜欢在这里传递一个元素,因为我觉得父母应该确定孩子在哪里可以放置内容。
为了渲染,我尽量保持Parent
视图非常简单。 我希望render
函数只能渲染父视图。 没有事件代表团,没有渲染子视图,什么都没有。 只是一个简单的渲染。
有时这并不总是工作。 例如在上面的例子中,只要模型中的名称发生更改,就需要更新#name
元素。 但是,这个块是ParentView
模板的一部分,并不是由一个专用的Child
视图处理,所以我解决了这个问题。 我将创build一些只replace#name
元素内容的subRender
函数,而不必垃圾整个#parent
元素。 这可能看起来像一个黑客,但我真的发现它比不必担心重新渲染整个DOM和重新附加元素等。 如果我真的想把它弄干净,我会创build一个新的Child
视图(类似于InfoView
)来处理#name
块。
现在,对于Child
视图, initialization
与Parent
视图非常相似,只是没有创build任何其他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
,我不必担心爆炸和重build整个块。
以下是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
函数中处理集合的更多逻辑。 你如何处理集合真的取决于你,但你至less需要听取收集事件,并决定如何渲染(追加/删除,或只是重新渲染整个块)。 我个人喜欢追加新视图,删除旧视图,而不是重新渲染整个视图。
PhoneView
将几乎与InfoView
完全相同,只能听取其关心的模型更改。
希望这有所帮助,请让我知道是否有任何混淆或不够详细。
我不确定这是否直接回答你的问题,但我认为这是相关的:
http://lostechies.com/derickbailey/2011/10/11/backbone-js-getting-the-model-for-a-clicked-element/
当然,我设立这篇文章的背景是不同的,但是我认为我提供的两个解决scheme,以及每个scheme的利弊,都应该让你朝着正确的方向前进。
对我来说,这似乎不是世界上最糟糕的想法,通过某种标志来区分你的观点的初始设置和随后的设置。 为了使这个干净和容易的国旗应该被添加到你自己的视图,应该扩展骨干(基地)视图。
和Derick一样我不完全确定这是否直接回答你的问题,但我认为在这方面至less值得一提。
另请参阅:在骨干中使用事件总线
Kevin Peel给出了一个很好的答案 – 这是我的tl; dr版本:
initialize : function () { //parent init stuff this.render(); //ANSWER: RENDER THE PARENT BEFORE INITIALIZING THE CHILD!! this.child = new Child(); },
我试图避免像这样的意见之间的耦合。 我通常有两种方法:
使用路由器
基本上,你让你的路由器函数初始化父视图和子视图。 所以这个视图是互不知道的,但是路由器处理这一切。
将相同的el传递给两个视图
this.parent = new Parent({el: $('.container-placeholder')}); this.child = new Child({el: $('.container-placeholder')});
两者都有相同的DOM的知识,你可以订购他们无论如何你想要的。
我所做的就是给每个孩子一个身份(哪个骨干已经为你做了:cid)
当Container进行渲染时,使用“cid”和“tagName”为每个孩子生成一个占位符,因此在模板中,孩子们不知道容器放在哪里。
<tagName id='cid'></tagName>
比你可以使用
Container.render() Child.render(); this.$('#'+cid).replaceWith(child.$el); // the rapalceWith in jquery will detach the element // from the dom first, so we need re-delegateEvents here child.delegateEvents();
不需要指定的占位符,Container只会生成占位符而不是子级的DOM结构。 Cotainer和Children仍然生成自己的DOM元素,只有一次。
这里是一个轻量级的混合创build和渲染子视图,我认为这个线程中的所有问题:
https://github.com/rotundasoftware/backbone.subviews
这个插件所采用的方法是在父视图第一次渲染后创build和渲染子视图。 然后,在父视图的后续渲染中,$ .detach子视图元素,重新渲染父视图,然后将子视图元素插入适当的位置并重新渲染它们。 这样,子视图对象就可以在后续的渲染中重用,而且不需要重新委托事件。
请注意,集合视图(集合中的每个模型都用一个子视图表示)的情况完全不同,并且我认为它有其自己的讨论/解决scheme。 我知道的最好的一般解决scheme是木偶中的CollectionView 。
编辑:对于集合视图的情况下,你可能也想看看这个更关注UI的实现 ,如果你需要select模型的基础上点击和/或拖放重新sorting。