如何处理在 Backbone.js 中初始化和渲染子视图?

我有三种不同的方法来初始化和呈现视图及其子视图,每一种都有不同的问题。我很好奇是否有更好的方法来解决所有的问题:


场景一:

在父类的Initialize函数中初始化子类。这样,渲染过程中就不会出现任何阻塞。

initialize : function () {


//parent init stuff


this.child = new Child();
},


render : function () {


this.$el.html(this.template());


this.child.render().appendTo(this.$('.container-placeholder');
}

存在的问题:

  • 最大的问题是,第二次在父对象上调用render将删除所有子对象的事件绑定。(这是因为jQuery的$.html()的工作方式。)这可以通过调用this.child.delegateEvents().render().appendTo(this.$el);来缓解,但是第一个,也是最常见的情况,你做了更多不必要的工作。

  • 通过附加子元素,可以强制呈现函数了解父元素DOM结构,以便获得所需的顺序。这意味着更改模板可能需要更新视图的渲染函数。


场景二:

仍然在父类的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();
}

问题:

  • 这意味着渲染函数现在必须与所有的初始化逻辑绑定在一起。

  • 如果我编辑一个子视图的状态,然后在父视图上调用render,一个全新的子视图将被创建,它当前的所有状态将丢失。这似乎也可能会导致内存泄漏。


我很想知道你们对这件事的看法。您会使用哪种场景?或者有没有第四种魔法可以解决所有这些问题?

你曾经跟踪过视图的渲染状态吗?比如renderedBefore标志?看起来真的很糟糕。

59973 次浏览

我不确定这是否直接回答了你的问题,但我认为这是相关的:

http://lostechies.com/derickbailey/2011/10/11/backbone-js-getting-the-model-for-a-clicked-element/

当然,我编写本文的背景是不同的,但是我认为我提供的两个解决方案,以及它们各自的优点和缺点,应该会让您朝着正确的方向前进。

在我看来,通过某种标志来区分视图的初始设置和后续设置并不是世界上最糟糕的主意。为了让这个过程简单明了,这个标志应该被添加到你自己的视图中,这个视图应该扩展骨干(基础)视图。

和Derick一样,我不完全确定这是否直接回答了你的问题,但我认为在这种情况下至少值得一提。

另见:在Backbone中使用Eventbus

这是一个很好的问题。Backbone很好,因为它缺乏假设,但它确实意味着您必须自己(决定如何)实现这样的东西。在查看了我自己的东西之后,我发现我(有点)混合使用了场景1和场景2。我不认为第四种神奇场景存在,因为简单地说,你在场景1中所做的一切;2 .必须做到。

我想用一个例子来解释我喜欢如何处理它是最简单的。假设我把这个简单的页面分成了指定的视图:

Page Breakdown

假设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,其中一个,#name,需要在某个位置设置。PhoneListView拥有自己的子视图,即PhoneView条目的数组。

现在回到你的问题上来。我根据视图类型处理不同的初始化和呈现。我将视图分为两种类型,Parent视图和Child视图。

它们之间的区别很简单,Parent视图包含子视图,而Child视图不包含子视图。所以在我的例子中,ParentViewPhoneListViewParent视图,而InfoViewPhoneView条目是Child视图。

就像我之前提到的,这两个类别之间最大的区别是何时允许渲染。在理想情况下,我希望Parent视图只呈现一次。当模型发生变化时,由它们的子视图来处理任何重新呈现。Child视图,另一方面,我允许在他们需要的任何时候重新渲染,因为他们没有任何其他视图依赖于他们。

更详细地说,对于Parent视图,我希望我的initialize函数做一些事情:

  1. 初始化我自己的视图
  2. 渲染我自己的视图
  3. 创建并初始化任何子视图。
  4. 在我的视图中为每个子视图分配一个元素(例如InfoView将被分配为#info)。

步骤1不言自明。

完成第2步,即呈现,以便在我尝试分配子视图所依赖的任何元素之前都已经存在。通过这样做,我知道所有的子events都将被正确设置,并且我可以随心所欲地重新渲染它们的块,而不必担心必须重新委托任何东西。这里我实际上没有render任何子视图,我允许它们在自己的initialization中这样做。

第3步和第4步实际上是在创建子视图时传入el时同时处理的。我喜欢在这里传递一个元素,因为我觉得父元素应该决定在它自己的视图中允许子元素放置其内容的位置。

对于渲染,我尽量保持Parent视图的简单。我希望render函数除了渲染父视图外什么也不做。没有事件委托,没有子视图的呈现,什么都没有。只是一个简单的渲染。

但有时这并不总是有效。例如,在上面的例子中,#name元素需要在模型中的名称发生变化时进行更新。然而,这个块是ParentView模板的一部分,并不是由专门的Child视图处理的,所以我绕过了它。我将创建某种subRender函数,用只有替换#name元素的内容,而不必丢弃整个#parent元素。这可能看起来像一个黑客,但我真的发现它比不得不担心重新渲染整个DOM和重新附加元素等更好。如果我真的想让它干净,我将创建一个新的Child视图(类似于InfoView)来处理#name块。

现在对于Child视图,initializationParent视图非常相似,只是没有进一步创建任何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,我不必担心爆破和重建整个块。

下面是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函数中多一点逻辑来处理集合。如何处理集合实际上取决于您,但您至少需要侦听集合事件并决定如何呈现(追加/删除,或只是重新呈现整个块)。我个人喜欢添加新的视图,删除旧的视图,而不是重新渲染整个视图。

PhoneView将与InfoView几乎相同,只是侦听它所关心的模型更改。

希望这对您有所帮助,如果有任何困惑或不够详细的地方,请告诉我。

我试图避免这样的视图之间的耦合。我通常有两种方法:

使用路由器

基本上,你让路由器函数初始化父视图和子视图。所以视图之间不知道彼此,但路由器处理了这一切。

将相同的el传递给两个视图

this.parent = new Parent({el: $('.container-placeholder')});
this.child = new Child({el: $('.container-placeholder')});

两者都具有相同DOM的知识,您可以随意对它们进行排序。

Kevin Peel给出了一个很好的答案——这是我的tl;dr版本:

initialize : function () {


//parent init stuff


this.render(); //ANSWER: RENDER THE PARENT BEFORE INITIALIZING THE CHILD!!


this.child = new Child();
},

我所做的是给每个孩子一个身份(Backbone已经为你做了:cid)

当Container进行渲染时,使用“cid”和“tagName”为每个子元素生成一个占位符,因此在模板中,子元素不知道它将被Container放在哪里。

<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();

不需要指定的占位符,容器只生成占位符而不是子DOM结构。container和child仍然生成自己的DOM元素,而且只生成一次。

下面是一个轻量级的mixin,用于创建和渲染子视图,我认为它解决了这个线程中的所有问题:

https://github.com/rotundasoftware/backbone.subviews

这个插件所采用的方法是在父视图呈现第一个之后创建并呈现子视图。然后,在父视图的后续呈现中,$.分离子视图元素,重新呈现父视图,然后在适当的位置插入子视图元素并重新呈现它们。通过这种方式,子视图对象在后续渲染中被重用,并且不需要重新委托事件。

注意,集合视图的情况(集合中的每个模型都用一个子视图表示)是完全不同的,我认为它有自己的讨论/解决方案。对于这种情况,我所知道的最佳通用解决方案是木偶中的CollectionView

编辑:对于集合视图的情况,如果你需要基于单击和/或拖放来重新排序选择模型,你可能还想检查这个更关注UI的实现