如何在Backbone.js中渲染和追加子视图

Ian*_*lor 133 javascript model-view-controller 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();
}
Run Code Online (Sandbox Code Playgroud)

优点:您不必担心通过追加维护正确的DOM顺序.视图在早期初始化,因此在渲染函数中不会同时执行所有操作.

缺点:你被迫重新委托事件(),这可能是昂贵的?父视图的渲染函数与所有需要发生的子视图渲染混杂在一起?您无法设置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);
}
Run Code Online (Sandbox Code Playgroud)

优点:您无需重新委派活动.您不需要仅包含空占位符的模板,并且您的tagName将返回由视图定义.

缺点:您现在必须确保以正确的顺序附加内容.子视图渲染仍然使父视图的渲染变得混乱.

有一个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();
}
Run Code Online (Sandbox Code Playgroud)

优点:子视图逻辑现在与视图的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();
}
Run Code Online (Sandbox Code Playgroud)

在所有这些示例中,我有点混合并匹配了许多不同的实践(很抱歉)但是你要保留或添加的是什么?你不会做什么?

实践摘要:

  • initialize或在中实例化子视图render
  • render或中执行所有子视图渲染逻辑onRender
  • 使用setElementappend/appendTo

Luk*_*kas 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();
    }
});
Run Code Online (Sandbox Code Playgroud)

这与您的第一个示例类似,只有一些更改:

  1. 附加子元素的顺序很重要
  2. 外部视图不包含要在内部视图上设置的html元素(这意味着您仍然可以在内部视图中指定tagName)
  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);
    }
});
Run Code Online (Sandbox Code Playgroud)

解决方案2可能看起来更干净,但它在我的经验中引起了一些奇怪的事情并且对性能产生了负面影响.

我通常使用解决方案1,原因有两个:

  1. 我的很多观点都依赖于他们的render()方法中已经存在于DOM中
  2. 重新呈现外部视图时,不必重新初始化视图,重新初始化可能导致内存泄漏,并且还会导致现有绑定出现怪异问题

请记住,如果您正在初始化new View()每次render()调用,那么初始化将会调用delegateEvents().所以这不一定是你所表达的"骗局".


Jos*_*zel 31

这是Backbone长期存在的问题,根据我的经验,这个问题并没有真正令人满意的答案.我与你分享你的挫折感,特别是因为尽管这个用例有多么普遍,但是引用的却很少.也就是说,我通常会选择类似于你的第二个例子.

首先,我会解雇任何需要你重新委托事件的事情.Backbone的事件驱动视图模型是其最关键的组件之一,并且仅仅因为您的应用程序非平凡而失去该功能会在任何程序员的口中留下不好的味道.所以划伤第一.

关于你的第三个例子,我认为这只是传统渲染实践的最终目的,并没有增加太多意义.也许如果你正在进行实际的事件触发(即,不是一个人为的onRender"事件"),那么将这些事件绑定到render自身是值得的.如果您发现render变得笨拙且复杂,则您的子视图太少.

回到你的第二个例子,这可能是三个邪恶中较小的一个.以下是我的PDF版本第42页上的Recipes With Backbone提取的示例代码:

...
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);
}
Run Code Online (Sandbox Code Playgroud)

这只是比第二个例子稍微复杂一点的设置:它们指定了一组函数,addAll并且addOne执行脏工作.我认为这种方法是可行的(我当然也使用它); 但它仍留下一种奇怪的回味.(原谅所有这些舌头的比喻.)

按照正确的顺序附加要点:如果你严格追加,那肯定是限制.但请确保您考虑所有可能的模板方案.也许您实际上喜欢占位符元素(例如,空div或者ul),然后您可以replaceWith使用包含相应子视图的新(DOM)元素.追加不是唯一的解决方案,如果你关心那么多,你当然可以解决订购问题,但我想你如果它绊倒你就会遇到设计问题.请记住,子视图可以包含子视图,如果合适,它们应该是.这样,你有一个相当树状的结构,这是非常好的:每个子视图按顺序添加所有子视图,然后父视图添加另一个,依此类推.

不幸的是,解决方案#2可能是您希望使用开箱即用的Backbone的最佳选择.如果你有兴趣检查第三方库,我已经研究过(但实际上还没有时间玩),那就是Backbone.LayoutManager,它似乎有一种更健康的方法来添加子视图.然而,即使他们最近就类似问题进行了辩论.

  • 倒数第二行--`mode.bind('remove',view.remove);` - 你不应该只在Appointment的初始化函数中这样做以保持它们分开吗? (3认同)
  • 那么当每次父级呈现时都无法重新实例化视图,因为它保持状态? (2认同)

Dan*_*man 5

感到惊讶的是还没有提到,但我会认真考虑使用木偶.

它强制多一点结构主干的应用程序,包括特定视图类型(ListView,ItemView,RegionLayout),加入适量的ControllerS和更大量.

这是Github上的项目,以及Addy Osmani在Backbone Fundamentals一书中的精彩指南,以帮助您入门.

  • 这不回答这个问题. (3认同)
  • @CeasarBautista我不介绍如何使用Marionette完成此任务,但Marionette确实解决了上述问题 (2认同)