在过去的六个月中,我一直在与Backbone合作。前两个月很混乱,学习并弄清楚我要如何围绕它构建代码。接下来的4个月,该公司推出了适合生产的应用程序。不要误会我的意思,Backbone使我摆脱了以前标准的成千上万行客户端代码的混乱,但是它使我能够在更短的时间内完成更多宏伟的事情,从而带来了一系列全新的问题。对于我在这里提出的所有问题,有一些简单的解决方案,感觉像是骇客或只是 错误 。我承诺提供300分的赏金,以提供出色的解决方案。开始:
并且我们使用了以下代码来模拟加载事件:
window.old_sync = Backbone.sync # Add a loading event to backbone.sync Backbone.sync = (method, model, options) -> old_sync(method, model, options) model.trigger("loading")
大。它按预期工作,但感觉不正确。我们将此事件绑定到所有相关视图,并显示一个加载图标,直到从该模型收到成功或错误事件为止。有没有更好,更聪明的方法呢?
现在为难者:
这是另一个示例:您要在集合中创建一个新项目。您按下“新”按钮,然后开始填写表格。您是否立即将项目添加到集合中?但是,如果您决定丢弃它,会发生什么?或者,如果您将整个收藏保存在另一个选项卡上?并且,有一个文件上传- 您需要先保存并同步模型,然后才能开始上传文件(以便可以将文件附加到模型)。因此,一切开始震撼地呈现:保存模型和列表,然后表单再次呈现自己- 现在已同步,因此您获得了一个新的Delete按钮,并在列表中显示-但现在文件上传完成了上传,因此一切重新开始渲染。
将子视图添加到混合中,一切开始看起来像Fellini电影。
它一直是子视图 - 这是一篇有关此内容的好文章。对于所有神圣的事物,我无法找到一种将jQuery插件或DOM事件附加到具有子视图的任何视图的正确方法。地狱随之而来。工具提示会听到渲染很长一段时间并开始变乱,子视图变得像僵尸一样或没有响应。这是实际错误所在的主要痛点,但是我仍然没有一个全面的解决方案。
闪烁 -渲染速度很快。实际上,它是如此之快,以至于我的屏幕看起来好像被卡住了。有时是必须重新加载图像(通过另一个服务器调用!),因此html最小化然后突然再次最大化-该元素的css width + height将解决此问题。有时我们可以使用fadeIn和fadeOut解决这个问题,因为这有时会很麻烦,因为有时我们会重用视图,有时会重新创建视图。
TL; DR- 我在Backbone中的视图和子视图遇到问题-它渲染太多次,渲染时闪烁,子视图使我的DOM事件分离并吞噬了我的大脑。
谢谢!
更多详细信息:BackboneJS与Ruby on Rails Gem。使用UnderscoreJS模板的模板。
为了最大程度地减少DOM层次结构的完整呈现,您可以在DOM中设置特殊的节点,以反映给定属性的更新。
让我们使用这个简单的Underscore模板,一个名称列表:
<ul> <% _(children).each(function(model) { %> <li> <span class='model-<%= model.cid %>-name'><%= model.name %></span> : <span class='model-<%= model.cid %>-name'><%= model.name %></span> </li> <% }); %> </ul>
注意class model-<%= model.cid %>-name,这将是我们的注入点。
model-<%= model.cid %>-name
然后,我们可以定义一个基本视图(或修改Backbone.View),以便在更新这些节点时为其添加适当的值:
var V = Backbone.View.extend({ initialize: function () { // bind all changes to the models in the collection this.collection.on('change', this.autoupdate, this); }, // grab the changes and fill any zone set to receive the values autoupdate: function (model) { var _this = this, changes = model.changedAttributes(), attrs = _.keys(changes); _.each(attrs, function (attr) { _this.$('.model-' + model.cid + '-' + attr).html(model.get(attr)); }); }, // render the complete template // should only happen when there really is a dramatic change to the view render: function () { var data, html; // build the data to render the template // this.collection.toJSON() with the cid added, in fact data = this.collection.map(function (model) { return _.extend(model.toJSON(), {cid: model.cid}); }); html = template({children: data}); this.$el.html(html); return this; } });
代码会有所不同,以适应模型而不是集合。与http://jsfiddle.net/nikoshr/cfcDX/一起玩的小提琴
将渲染委派给子视图可能会花费很多,它们的HTML片段必须插入到父视图的DOM中。看看这个jsperf测试,比较不同的渲染方法
其要点在于,生成完整的HTML结构然后应用视图比构建视图和子视图然后级联呈现要快得多。例如,
<script id="tpl-table" type="text/template"> <table> <thead> <tr> <th>Row</th> <th>Name</th> </tr> </thead> <tbody> <% _(children).each(function(model) { %> <tr id='<%= model.cid %>'> <td><%= model.row %></td> <td><%= model.name %></td> </tr> <% }); %> </tbody> </table> </script> var ItemView = Backbone.View.extend({ }); var ListView = Backbone.View.extend({ render: function () { var data, html, $table, template = this.options.template; data = this.collection.map(function (model) { return _.extend(model.toJSON(), { cid: model.cid }); }); html = this.options.template({ children: data }); $table = $(html); this.collection.each(function (model) { var subview = new ItemView({ el: $table.find("#" + model.cid), model: model }); }); this.$el.empty(); this.$el.append($table); return this; } }); var view = new ListView({ template: _.template($('#tpl-table').html()), collection: new Backbone.Collection(data) });
http://jsfiddle.net/nikoshr/UeefE/
请注意,jsperf显示了可以将模板拆分为子模板而不会带来太多损失,这将使您能够为行提供部分渲染。
值得一提的是,不要在连接到DOM的节点上工作,这将导致不必要的重排。在操作它之前,要么创建一个新的DOM,要么分离该节点。
德里克·贝利(DerickBailey)撰写了一篇出色的文章,主题是消除僵尸观点
基本上,您必须记住,丢弃视图时,必须取消绑定所有侦听器并执行任何其他清除操作,例如销毁jQuery插件实例。我使用的是类似于Derick在Backbone.Marionette中使用的方法的组合:
var BaseView = Backbone.View.extend({ initialize: function () { // list of subviews this.views = []; }, // handle the subviews // override to destroy jQuery plugin instances unstage: function () { if (!this.views) { return; } var i, l = this.views.length; for (i = 0; i < l; i = i + 1) { this.views[i].destroy(); } this.views = []; }, // override to setup jQuery plugin instances stage: function () { }, // destroy the view destroy: function () { this.unstage(); this.remove(); this.off(); if (this.collection) { this.collection.off(null, null, this); } if (this.model) { this.model.off(null, null, this); } } });
更新我之前的示例以使行具有可拖动的行为,如下所示:
var ItemView = BaseView.extend({ stage: function () { this.$el.draggable({ revert: "invalid", helper: "clone" }); }, unstage: function () { this.$el.draggable('destroy'); BaseView.prototype.unstage.call(this); } }); var ListView = BaseView.extend({ render: function () { //same as before this.unstage(); this.collection.each(function (model) { var subview = new ItemView({ el: $table.find("#" + model.cid), model: model }); subview.stage(); this.views.push(subview); }, this); this.stage(); this.$el.empty(); this.$el.append($table); return this; } });
http://jsfiddle.net/nikoshr/yL7g6/
销毁根视图将遍历视图的层次结构并执行必要的清理。
注意:对JS代码感到抱歉,我对Coffeescript不够熟悉,无法提供准确的摘要。