首页 文章

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

提问于
浏览
133

我有一个嵌套的视图设置,可以在我的应用程序中得到一些深度 . 有很多方法我可以想到初始化,渲染和追加子视图,但我想知道常见的做法是什么 .

Here are a couple I've thought of:

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

Pros: 您在渲染功能中一次完成所有操作 .

Cons: 您被迫重新委托事件(),这可能代价高昂?父视图's render function is cluttered with all of the subview rendering that needs to happen? You don' t能够设置元素的 tagName ,因此模板需要维护正确的tagNames .

Another way:

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

Pros: 您不需要一个只包含空占位符的模板,并且您的tagName将返回由视图定义 .

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

使用 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();
}

Pros: 子视图逻辑现在与视图的 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

10 回答

  • 0

    感到惊讶的是't been mentioned yet, but I' d认真考虑使用Marionette .

    它为Backbone应用程序强制执行更多结构,包括特定的视图类型( ListViewItemViewRegionLayout ),添加适当的 Controller 等等 .

    这是the project on Github和一个伟大的guide by Addy Osmani in the book Backbone Fundamentals来帮助你入门 .

  • 4

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

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

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

    • 附加子元素的顺序很重要

    • 外部视图不包含要在内部视图上设置的html元素(意味着您仍然可以在内部视图中指定tagName)

    • render() 在内部视图之后调用's element has been placed into the DOM, which is helpful if your inner view' s render() 方法根据其他元素的位置/大小在页面上放置/调整大小(根据我的经验,这是一个常见的用例)

    Solution 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,原因有两个:

    • 我的很多观点都依赖于他们在 render() 方法中的DOM

    • 重新渲染外部视图时,不必重新初始化视图,重新初始化可能导致内存泄漏,并且还会导致现有绑定出现怪异问题

    请记住,如果每次调用 render() 时正在初始化 new View() ,那么无论如何,该初始化将调用 delegateEvents() . 所以不应该表达出来 .

  • 2

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

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

    关于你的第三个例子,我认为's just an end-run around the conventional rendering practice and doesn' t增加了很多意思 . 也许如果你正在进行实际的事件触发(即,不是一个人为的“ 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);
    }
    

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

    按照正确的顺序附加你的观点:如果你是一个限制 . 但请确保您考虑所有可能的模板方案 . 也许您实际上喜欢占位符元素(例如,空 divul ),然后您可以replaceWith一个包含相应子视图的新(DOM)元素 . 追加isn 't the only solution, and you can certainly get around the ordering problem if you care about it that much, but I would imagine you have a design issue if it is tripping you up. Remember, subviews can have subviews, and they should if it'是合适的 . 那样的话,你有一个相当的树状结构,非常好:每个子视图在父视图添加另一个之前按顺序添加其所有子视图,依此类推 .

    不幸的是,解决方案#2可能是您希望使用开箱即用的Backbone的最佳选择 . 如果你实际上还有时间玩的话是Backbone.LayoutManager,这似乎有一种更 Health 的方法来添加子视图 . 然而,即使他们在类似的问题上也有这些问题 .

  • 0

    我相信,这是一个非常全面的解决方案 . 它允许集合中的模型更改,并且仅重新呈现其视图(而不是整个集合) . 它还通过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
    });
    
  • 58

    查看此mixin以创建和呈现子视图:

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

    它是一个极简主义的解决方案,解决了该线程中讨论的许多问题,包括呈现顺序,不必重新委托事件等 . 请注意集合视图的情况(集合中的每个模型都用一个表示) subview)是一个不同的主题 . 我知道的最佳通用解决方案是CollectionView in Marionette .

  • 0

    我真的不喜欢上述任何解决方案 . 我更喜欢在每个视图上进行此配置,必须在render方法中手动完成工作 .

    • views 可以是返回视图定义对象的函数或对象

    • 当调用父级的 .remove 时,应该调用最低级别的嵌套子级的 .remove (从子子视图一直到)

    • 默认情况下,父视图会传递它自己的模型和集合,但可以添加和覆盖选项 .

    这是一个例子:

    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,因此没有关于此问题和许多其他问题的实践 . 它意味着尽可能不受任何影响 . 从理论上讲,您甚至不必使用Backbone模板 . 您可以在视图的 render 函数中使用javascript / jquery来手动更改视图中的所有数据 . 为了使它更加极端,你甚至不需要一个特定的 render 功能 . 您可以使用一个名为 renderFirstName 的函数来更新dom中的第一个名称,使用 renderLastName 来更新dom中的姓氏 . 如果你采用这种方法,那么在性能方面会更好,你永远不必再次手动委派事件 . 代码对于阅读它的人来说也是完全有意义的(虽然它会更长/更乱码) .

    但是,通常使用模板并简单地破坏和重建整个视图以及每次渲染调用的子视图都没有任何缺点,因为提问者甚至不会做任何事情 . 这就是大多数人为他们遇到的每种情况所做的事情 . 这就是为什么固定框架只会使这成为默认行为 .

  • 5

    您还可以将渲染的子视图作为变量注入主模板中作为变量 .

    首先渲染子视图并将它们转换为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() {
        } });
    
  • 31

    由于成本高昂,因此无需重新委托事件 . 见下文:

    var OuterView = Backbone.View.extend({
        initialize: function() {
            this.inner = new InnerView();
        },
    
        render: function() {
            // first detach subviews            
            this.inner.$el.detach(); 
    
            // now can set html without affecting subview element's events
            this.$el.html(template);
    
            // now render and attach subview OR can even replace placeholder 
            // elements in template with the rendered subview element
            this.$el.append(this.inner.render().el);
    
        }
    });
    
    var InnerView = Backbone.View.extend({
        render: function() {
            this.$el.html(template);            
        }
    });
    

相关问题