2012-02-14 81 views
132

我有一个嵌套的视图设置,它可以在我的应用程序中获得一些深度。有很多方法可以考虑初始化,渲染和附加子视图,但我不知道常用的做法是什么。如何在Backbone.js中渲染和附加子视图

这里有几个我认为:

initialize : function() { 

    this.subView1 = new Subview({options}); 
    this.subView2 = new Subview({options}); 
}, 

render : function() { 

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

    this.subView1.setElement('.some-el').render(); 
    this.subView2.setElement('.some-el').render(); 
} 

优点:您不必担心与附加保持正确的DOM顺序。视图在初始化时就被初始化了,所以在渲染函数中不需要一次完成所有的事情。

缺点:您被迫重新委托Events(),这可能是昂贵的?父视图的渲染函数与需要发生的所有子视图渲染混淆?您无法设置元素的tagName,因此模板需要维护正确的tagNames。

另一种方式:

initialize : function() { 

}, 

render : function() { 

    this.$el.empty(); 

    this.subView1 = new Subview({options}); 
    this.subView2 = new Subview({options}); 

    this.$el.append(this.subView1.render().el, this.subView2.render().el); 
} 

优点:您不必重新授权事件。您不需要只包含空白占位符的模板,并且您的标记名已重新由视图定义。

缺点:您现在必须确保以正确的顺序追加内容。父视图的渲染仍然被子视图渲染混乱。

随着onRender事件:

initialize : function() { 
    this.on('render', this.onRender); 
    this.subView1 = new Subview({options}); 
    this.subView2 = new Subview({options}); 
}, 

render : function() { 

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

    //other stuff 

    return this.trigger('render'); 
}, 

onRender : function() { 

    this.subView1.setElement('.some-el').render(); 
    this.subView2.setElement('.some-el').render(); 
} 

优点:的子视图逻辑现在从视图的render()方法分离。

随着onRender事件:

initialize : function() { 
    this.on('render', this.onRender); 
}, 

render : function() { 

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

    //other stuff 

    return this.trigger('render'); 
}, 

onRender : function() { 
    this.subView1 = new Subview(); 
    this.subView2 = new Subview(); 
    this.subView1.setElement('.some-el').render(); 
    this.subView2.setElement('.some-el').render(); 
} 

我有种混合并在所有的这些例子(关于很抱歉),但什么是你将保持者或匹配一堆不同的做法加?你会怎么做?

总结的做法:在initializerender

  • 实例化的子视图?
  • 执行renderonRender中的所有子视图渲染逻辑?
  • 使用setElementappend/appendTo
+0

我会小心新的没有删除,你有内存泄漏在那里。 – vimdude 2012-02-14 04:14:48

+1

不用担心,我有一个'close'方法和'onClose'来清理孩子,但我只是对如何实例化和渲染它们而感到好奇。 – 2012-02-14 07:33:32

+3

@abdelsaid:在JavaScript中,GC处理内存的重新分配。 JS中的'delete'与C++中的'delete'不一样。如果你问我,这是一个非常糟糕的关键字。 – 2012-07-31 21:18:12

回答

30

这是Backbone的一个长期问题,根据我的经验,这个问题并没有真正令人满意的答案。我分享你的挫败感,尤其是因为尽管这个用例有多常见,所以指导得不多。也就是说,我通常会跟第二个例子类似。

首先,我会忽略任何需要重新委托事件的事情。 Backbone的事件驱动的视图模型是其中最重要的组件之一,并且仅仅因为你的应用程序不重要就会失去这种功能,会给任何程序员留下不好的口味。所以从头开始吧。

关于你的第三个例子,我认为这只是围绕着传统渲染练习的一个结局,并没有增加太多的意义。也许如果你正在做实际的事件触发(也就是不是人为的“onRender”事件),那么值得将这些事件绑定到render本身。如果您发现render变得笨重和复杂,您的子视图太少。

回到你的第二个例子,这可能是三个邪恶中较小的一个。下面是示例代码从Recipes With Backbone解除,我PDF版第42页上找到:

... 
render: function() { 
    $(this.el).html(this.template()); 
    this.addAll(); 
    return this; 
}, 
    addAll: function() { 
    this.collection.each(this.addOne); 
}, 
    addOne: function(model) { 
    view = new Views.Appointment({model: model}); 
    view.render(); 
    $(this.el).append(view.el); 
    model.bind('remove', view.remove); 
} 

这只是一个稍微复杂的设置比你的第二个例子:他们specifiy一组功能,addAlladdOne,该做的肮脏的工作。我认为这种方法是可行的(我当然使用它);但它仍然留下一个奇怪的余味。 (请原谅所有这些舌头的隐喻。)

要按照正确的顺序添加点:如果你严格追加,肯定,这是一个限制。但请确保您考虑所有可能的模板方案。也许你实际上喜欢一个占位符元素(例如,一个空的divul),然后replaceWith一个新的(DOM)元素包含适当的子视图。追加并不是唯一的解决方案,如果你关心这个问题,你当然可以解决订购问题,但是如果它让你失望,我会想象你有一个设计问题。请记住,子视图可以有子视图,如果适合,它们应该是。这样,你就有了一个相当像树的结构,这非常好:每个子视图按顺序添加其所有子视图,然后在父视图添加另一个子视图,等等。

不幸的是,解决方案#2可能是您使用开箱即用Backbone的最佳选择。如果您有兴趣查看第三方库,那么我已经查看过(但实际上还没有任何时间可以玩),它是Backbone.LayoutManager,这似乎有更健康的方法来添加子视图。然而,即使他们已经有类似的问题recent debates这些。

+3

倒数第二行 - 'model.bind('remove',view.remove);' - 你不应该在Appointment的初始化函数中这样做以保持它们的分离吗? – ash 2012-05-08 06:34:33

+2

什么时候视图不能重新实例化每次它的父母呈现,因为它保持一个状态? – mor 2013-10-11 15:25:01

+0

停止所有这些疯狂,只使用[Backbone.subviews](https://github.com/rotundasoftware/backbone.subviews)插件! – 2014-01-10 19:10:57

58

我一般都见过/使用几个不同的解决方案:

解决方案1 ​​

var OuterView = Backbone.View.extend({ 
    initialize: function() { 
     this.inner = new InnerView(); 
    }, 

    render: function() { 
     this.$el.html(template); // or this.$el.empty() if you have no template 
     this.$el.append(this.inner.$el); 
     this.inner.render(); 
    } 
}); 

var InnerView = Backbone.View.extend({ 
    render: function() { 
     this.$el.html(template); 
     this.delegateEvents(); 
    } 
}); 

这类似于你的第一个例子,有几个变化:

  1. 添加子元素的顺序很重要
  2. 外部视图不包含n中的HTML元素在内期(县)设置(这意味着你仍然可以在内部视图指定标签名)
  3. render()被调用后内视图的元素已经被放置到DOM,如果你内心是有帮助视图的render()方法是基于其他元素的位置/大小(这是一种常见的情况,在我的经验)

溶液2

var OuterView = Backbone.View.extend({ 
    initialize: function() { 
     this.render(); 
    }, 

    render: function() { 
     this.$el.html(template); // or this.$el.empty() if you have no template 
     this.inner = new InnerView(); 
     this.$el.append(this.inner.$el); 
    } 
}); 

var InnerView = Backbone.View.extend({ 
    initialize: function() { 
     this.render(); 
    }, 

    render: function() { 
     this.$el.html(template); 
    } 
}); 

溶液2放置/页面上的浆纱本身可能看起来更干净,但它造成了一些st根据我的经验,对业绩产生负面影响。

我一般采用方案一,为一对夫妇的原因:

  1. 我的很多观点依靠在DOM他们render()方法
  2. 当外视图进行重新渲染已经是,意见没有被重新初始化,这将重新初始化可能导致内存泄漏,并与现有的绑定

导致古怪的问题请记住,如果你每次初始化new View()render()是称为,初始化无论如何将打电话给delegateEvents()。正如你所表达的那样,这不一定是“骗局”。

+3

+1解决方案1 ​​ – 2013-07-21 04:16:33

+1

这些解决方案都不会调用View.remove的子视图树,这对于在视图中执行自定义清理至关重要,否则这些清理操作可能会阻止垃圾回收。 – 2015-06-11 14:33:08

2

看看这个混入用于创建和渲染子视图:

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

它是不是有一个最低限度的解决方案,解决了很多在这个线程讨论的问题,包括绘制顺序,重新授权事件等。请注意,集合视图(集合中的每个模型都用一个子视图表示)的情况是不同的主题。我知道的最佳通用解决方案是CollectionView in Marionette

4

我有,我认为是,一个比较全面的解决这个问题。它允许集合中的模型进行更改,并且只重新呈现其视图(而不是整个集合)。它还通过close()方法处理僵尸视图的移除。

var SubView = Backbone.View.extend({ 
    // tagName: must be implemented 
    // className: must be implemented 
    // template: must be implemented 

    initialize: function() { 
     this.model.on("change", this.render, this); 
     this.model.on("close", this.close, this); 
    }, 

    render: function(options) { 
     console.log("rendering subview for",this.model.get("name")); 
     var defaultOptions = {}; 
     options = typeof options === "object" ? $.extend(true, defaultOptions, options) : defaultOptions; 
     this.$el.html(this.template({model: this.model.toJSON(), options: options})).fadeIn("fast"); 
     return this; 
    }, 

    close: function() { 
     console.log("closing subview for",this.model.get("name")); 
     this.model.off("change", this.render, this); 
     this.model.off("close", this.close, this); 
     this.remove(); 
    } 
}); 
var ViewCollection = Backbone.View.extend({ 
    // el: must be implemented 
    // subViewClass: must be implemented 

    initialize: function() { 
     var self = this; 
     self.collection.on("add", self.addSubView, self); 
     self.collection.on("remove", self.removeSubView, self); 
     self.collection.on("reset", self.reset, self); 
     self.collection.on("closeAll", self.closeAll, self); 
     self.collection.reset = function(models, options) { 
      self.closeAll(); 
      Backbone.Collection.prototype.reset.call(this, models, options); 
     }; 
     self.reset(); 
    }, 

    reset: function() { 
     this.$el.empty(); 
     this.render(); 
    }, 

    render: function() { 
     console.log("rendering viewcollection for",this.collection.models); 
     var self = this; 
     self.collection.each(function(model) { 
      self.addSubView(model); 
     }); 
     return self; 
    }, 

    addSubView: function(model) { 
     var sv = new this.subViewClass({model: model}); 
     this.$el.append(sv.render().el); 
    }, 

    removeSubView: function(model) { 
     model.trigger("close"); 
    }, 

    closeAll: function() { 
     this.collection.each(function(model) { 
      model.trigger("close"); 
     }); 
    } 
}); 

用法:

var PartView = SubView.extend({ 
    tagName: "tr", 
    className: "part", 
    template: _.template($("#part-row-template").html()) 
}); 

var PartListView = ViewCollection.extend({ 
    el: $("table#parts"), 
    subViewClass: PartView 
}); 
5

惊讶这还没有被提到,但我会认真考虑使用Marionette

它强制多一点结构主干的应用程序,包括特定视图类型(ListViewItemViewRegionLayout),加入适当的Controller秒和更大量。

这是the project on Github和一个伟大的guide by Addy Osmani in the book Backbone Fundamentals让你开始。

+2

这并不回答问题。 – 2015-02-02 20:16:32

+2

@CeasarBautista我不介绍如何使用木偶来实现这一点,但木偶确实解决了上述问题 – 2015-02-03 00:28:05

0

我不太喜欢任何上述解决方案。我更喜欢这种配置,每个视图必须在渲染方法中手动执行。

  • views可以是函数或对象从分次返回的视图定义
  • 当父母的.remove被调用时,从最低阶了嵌套子的.remove应该被称为对象(一路-sub views)
  • 默认情况下,父视图传递它自己的模型和集合,但可以添加和覆盖选项。

下面是一个例子:

views: { 
    '.js-toolbar-left': CancelBtnView, // shorthand 
    '.js-toolbar-right': { 
     view: DoneBtnView, 
     append: true 
    }, 
    '.js-notification': { 
     view: Notification.View, 
     options: function() { // Options passed when instantiating 
      return { 
       message: this.state.get('notificationMessage'), 
       state: 'information' 
      }; 
     } 
    } 
} 
0

骨干有意建立这样,有就在这问候等诸多问题没有“通用”的做法。它意味着尽可能不被选择。理论上来说,你甚至不必在Backbone中使用模板。您可以在视图的render函数中使用javascript/jquery手动更改视图中的所有数据。为了让它更加极端,你甚至不需要一个特定的render函数。你可以有一个名为renderFirstName的函数,它更新了dom中的名字和renderLastName,它更新了dom中的姓。如果采取这种方法,性能方面会更好,并且您不必再次手动委派事件。该代码也会让读者阅读它的总体意义(尽管它会更长/更混乱的代码)。

但是,通常不存在使用模板和简单地销毁和重建整个视图及其每个渲染调用的子视图的缺点,因为提问者甚至不会发生其他情况。所以这就是大多数人所遇到的几乎所有情况。这就是为什么自以为是的框架只是将其作为默认行为。

0

您也可以将渲染的子视图作为变量注入主模板作为变量。

第一呈现子视图,并将其转换为HTML这样的:

var subview1 = $(subview1.render.el).html(); var subview2 = $(subview2.render.el).html();

(这样你也可以动态字符串拼接像subview1 + subview2的意见,循环使用),然后把它传递给主模板看起来像这样: ... some header stuff ... <%= sub1 %> <%= sub2 %> ... some footer stuff ...

,最后注入像这样:

this.$el.html(_.template(MasterTemplate, { sub1: subview1, sub2: subview2 }));

关于子视图中的事件:它们很可能必须在父级(masterView)中使用此方法连接,而不在子视图内。

0

我喜欢使用以下方法,它也确保正确删除子视图。这是Addy Osmani的book的一个例子。

Backbone.View.prototype.close = function() { 
    if (this.onClose) { 
     this.onClose(); 
    } 
    this.remove(); }; 

NewView = Backbone.View.extend({ 
    initialize: function() { 
     this.childViews = []; 
    }, 
    renderChildren: function(item) { 
     var itemView = new NewChildView({ model: item }); 
     $(this.el).prepend(itemView.render()); 
     this.childViews.push(itemView); 
    }, 
    onClose: function() { 
     _(this.childViews).each(function(view) { 
     view.close(); 
     }); 
    } }); 

NewChildView = Backbone.View.extend({ 
    tagName: 'li', 
    render: function() { 
    } });