如何在主干网中渲染和追加子视图.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(),这可能代价高昂?父视图的呈现功能与所有需要发生的子视图呈现杂乱无章?您无法设置元素,因此模板需要维护正确的 tagNames。tagName

另一种方式:

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

优点:您不必重新委派事件。您不需要仅包含空占位符的模板,并且您的 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();
}

优点:子视图逻辑现在与视图的方法分离。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

答案 1

我通常看到/使用了几种不同的解决方案:

解决方案 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. 外部视图不包含要在内部视图上设置的 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);
    }
});

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

我通常使用解决方案 1,原因如下:

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

请记住,如果您正在初始化每次调用,则该初始化无论如何都会调用。所以这不一定是一个“骗局”,正如你所表达的那样。new View()render()delegateEvents()


答案 2

这是Backbone的一个长期存在的问题,根据我的经验,这个问题并没有真正令人满意的答案。我和你一样感到沮丧,特别是因为尽管这个用例很常见,但指导却很少。也就是说,我通常会使用类似于您的第二个示例的内容。

首先,我会立即驳回任何需要你重新委派活动的事情。Backbone的事件驱动视图模型是其最关键的组件之一,仅仅因为你的应用程序不是微不足道的而失去该功能,就会给任何程序员留下不好的味道。所以刮第一。

关于你的第三个例子,我认为它只是围绕传统渲染实践的最终运行,并没有增加太多意义。也许如果你正在做实际的事件触发(即,不是人为的“”事件),那么值得将这些事件绑定到自身。如果您发现变得笨拙而复杂,那么您的子视图太少了。onRenderrenderrender

回到你的第二个例子,这可能是三害相权取其轻。以下是从 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);
}

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

关于以正确的顺序追加的观点:如果你严格追加,当然,这是一个限制。但请确保考虑所有可能的模板方案。也许你真的想要一个占位符元素(例如,一个空的或),然后你可以用一个包含相应子视图的新(DOM)元素替换它。追加并不是唯一的解决方案,如果你非常关心它,你当然可以绕过排序问题,但我想如果它把你绊倒了,你就会遇到设计问题。请记住,子视图可以有子视图,如果合适,它们应该具有子视图。这样,您就有了一个相当树状的结构,这非常好:每个子视图都按顺序添加其所有子视图,然后在父视图添加另一个子视图之前,依此类推。divul

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