如何处理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包含两个子视图, InfoViewPhoneListView以及一些额外的div,其中之一需要在某个点设置#namePhoneListView保存其自己的子视图,一个PhoneView条目数组。

所以到你的实际问题。 我基于视图types处理初始化和渲染。 我把我的看法分成两种types, Parent视图和Child视图。

他们之间的区别很简单, Parent视图保持子视图,而Child视图不能。 所以在我的例子中, ParentViewPhoneListViewParent视图,而InfoViewPhoneView则是Child视图。

就像我之前提到的那样,这两个类别之间最大的区别是它们被允许渲染。 在一个完美的世界中,我希望Parent视图只能渲染一次。 当模型发生变化时,由子视图来处理任何重新渲染。 另一方面, Child意见,我允许任何时候重新渲染,因为他们没有任何其他意见依赖于他们。

再详细一点,对于Parent视图,我喜欢我的initialize函数来做一些事情:

  1. 初始化我自己的观点
  2. 呈现我自己的观点
  3. 创build并初始化任何子视图。
  4. 为我的视图中的每个子视图分配一个元素(例如, 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视图, initializationParent视图非常相似,只是没有创build任何其他Child视图。 所以:

  1. 初始化我的观点
  2. 安装程序绑定侦听对我关心的模型的任何更改
  3. 呈现我的观点

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 ,您只需要在initializationrender函数中处理集合的更多逻辑。 你如何处理集合真的取决于你,但你至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。