BackboneJS渲染问题

在过去的六个月里,我一直在与Backbone合作。 前两个月都在搞乱,学习和弄清楚我想如何构build我的代码。 接下来的4个月里,一个生产适合的应用程序正在冲击。 不要误会我的意思,Backbone已经把我从之前标准的数千行客户端代码中解救了出来,但是它使我能够在更短的时间内完成更多macros大的事情,从而开辟了一堆新的问题。 对于我在这里提出的所有问题,都有一些简单的解决scheme,就像黑客或者只是觉得错误 。 我保证300分的奖励一个真棒解决scheme。 开始:

  1. 加载 – 对于我们的用例(pipe理面板),悲观同步是不好的。 对于某些事情,我需要在接受它们之前在服务器上validation事物。 我们在'sync'事件被合并到Backbone之前就开始了,

我们用这个小代码来模拟加载事件:

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") 

大。 它按预期工作,但不正确。 我们将此事件绑定到所有相关视图,并显示一个加载图标,直到我们从该模型中收到成功或错误事件。 有没有更好,更清洁的方法来做到这一点?

现在对于困难的人来说:

  1. 太多的东西渲染自己太多 – 比方说,我们的应用程序有标签。 每个选项卡控制一个集合。 在左边你收集。 你点击一个模型开始在中心编辑它。 您更改其名称并按Tab键以转到下一个表单项目。 现在,你的应用程序是一个“实时的东西”,注意到差异,运行validation,并自动同步更改到服务器,不需要保存button! 太好了,但是表格的开始处的H2与input中的名称相同 – 您需要更新它。 哦,你需要更新名单上的名字。 俄亥俄州,名单按名称sorting!

这是另一个例子:你想在集合中创build一个新的项目。 你按下“新build”button,你开始填写表格。 你是否立即将该项目添加到集合? 但是,如果你决定放弃它会发生什么? 或者如果您将整个集合保存在另一个选项卡上? 而且,还有一个file upload – 您需要保存和同步模型,然后才能开始上传文件(以便您可以将文件附加到模型)。 所以一切都开始渲染震动:你保存模型,列表和表单再次呈现自己 – 它现在同步,所以你得到一个新的删除button,它显示在列表中 – 但现在file upload完成上传,所以一切再次开始渲染。

添加子视图的混合,一切开始看起来像一个费里尼电影。

  1. 这是子视图一路下来 – 这是一个很好的文章关于这个东西 。 对于所有圣洁的事物,我都无法find一种将jQuery插件或DOM事件附加到任何具有子视图的视图的正确方法。 地狱及时发生。 工具提示听到一个很长的渲染,开始四处发作,子视图变成僵尸状或不回应。 这是实际错误的主要问题,但我仍然没有一个全面的解决scheme。

  2. 闪烁 – 渲染速度很快。 事实上,它的速度太快了,我的屏幕看起来像是一个癫痫发作。 有时候它是需要重新加载的图片(使用另一个服务器调用!),所以html最小化,然后突然再次最大化 – 该元素的css宽度+高度将修复该问题。 有时候我们可以用淡入淡出和淡出淡出来解决这个问题,因为有时我们会重复使用视图,有时重新创build视图。

TL; DR – 我在Backbone中遇到了视图和子视图的问题 – 呈现次数太多,呈现时闪烁,子视图分离我的DOM事件并吃掉我的大脑。

谢谢!

更多细节:Ruby on Rails Gem的BackboneJS。 使用UnderscoreJS模板的模板。

部分呈现的意见

为了最大限度地减lessDOM层次结构的完整渲染,可以在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> 

注意类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/

限制DOM操作

将渲染委托给子视图可能代价高昂,它们的HTML片段必须插入到父级的DOM中。 看看这个jsperftesting比较不同的渲染方法

它的要点是,生成完整的HTML结构,然后应用视图比构build视图和子视图,然后级联渲染快得多。 例如,

 <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的节点上工作,这将导致不必要的重排。 创build一个新的DOM或者在操作之前先分离节点。

压制僵尸

Derick Bailey写了一篇关于根除僵尸意见的文章

基本上,你必须记住,当你放弃一个视图,你必须解除所有的监听器,并执行任何额外的清理,如销毁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/

销毁根视图将遍历视图的层次结构并执行必要的清理。

NB:对于JS代码感到抱歉,我不太了解Coffeescript来提供准确的代码片段。

好的,为了… 🙂

  1. 载入中…

如果你想validation存储在服务器上的数据,最好的做法是在服务器端进行。 如果在服务器上validation失败,服务器不应该发送200个HTTP代码,因此保存Backbone.Model的方法会触发错误。

另一方面,对于validation数据主干还没有实现validation方法。 我想这是正确的select来实施和使用它。 但请记住,在设置和保存之前调用validate,如果validate返回错误,则set和save不会继续,并且不会修改模型属性。 失败的validation触发“错误”事件。

另一种方法,当我们调用沉默集(with {silent:true} param)时,我们应该手动调用isValid方法来validation数据。

  1. 太多的东西渲染自己太多..

你必须根据自己的逻辑分开你的观点。 收集的好习惯是每个模型的独立视图。 在这种情况下,您可以独立渲染每个元素。 甚至更多 – 当您启动您的容器视图进行收集时,您可以将集合中每个模型的任何事件绑定到适当的视图,并自动进行渲染。

太好了,但是表格的开始处的H2与input中的名称相同 – 您需要更新它。 哦,你需要更新名单上的名字。

你可以在方法上使用JQuery来实现发送值显示的callback。 例:

 //Container view init: function() { this.collection = new Backbone.Collection({ url: 'http://mybestpage.com/collection' }); this.collection.bind('change', this.render, this); this.collection.fetch(); }, render: function() { _.each(this.collection.models, function(model) { var newView = new myItemView({ model: model, name: 'view' + model.id }); this.$('#my-collection').append(newView.render().$el); view.on('viewEdit', this.displayValue); }, this); }, ... displayValue: function(value) { //method 1 this.displayView.setText(value); //we can create little inner view before, //for text displaying. Сonvenient at times. this.displayView.render(); //method 2 $(this.el).find('#display').html(value); } //View from collection myItemView = Backbone.View.extend({ events: { 'click #edit': 'edit' }, init: function(options) { this.name = options.name; }, ... edit: function() { this.trigger('viewEdit', this.name, this); } 

俄亥俄州,名单按名称sorting!

您可以使用sorting方法进行骨干收集。 但是(!)调用sorting会触发集合的“重置”事件。 通过{沉默:真}来避免这一点。 如何

这是另一个例子:你想在集合中创build一个新的项目…

当我们按下“新build”button时,我们需要创build一个新的模型,但只有.save()方法会触发成功,我们应该把这个模型推到集合中。 在另一种情况下,我们应该显示错误信息 当然,我们没有理由在我们的集合中添加一个新的模型,直到它被validation并保存在服务器上。

  1. 这是子视图一路下来…子视图变得僵尸般的或不回应。

当你(或任何模型)调用渲染方法时,它里面的所有元素都将被重新创build。 所以如果你有子视图,你应该调用subView.delegateEvents(subView.events); 所有子视图 可能这种方法是一个小窍门,但它的作品。

  1. 闪烁..

在大量和中等的图像中使用缩略图将使大量情况下的闪烁最小化。 其他方式,您可以将视图渲染分离为图像和其他内容。

例:

 var smartView = Backbone.View.extend({ initialize: function(){ this.model.on( "imageUpdate", this.imageUpdate, this ); this.model.on( "contentUpdate", this.contentUpdate, this ); }, render: function(){ this.$el.html(this.template(this.model.toJSON())); }, imageUpdate: function(){ this.$el.find('#image').attr('src', this.model.get('imageUrl')); }, contentUpdate: function(){ this.$el.find('#content').html(this.model.get('content')); } }) 

我希望这可以帮助任何人。 对不起,语法错误,如果有的话:)

载入中…

我是一个渴望加载的巨大粉丝。 我所有的服务器调用都是JSON响应,所以经常使用它们并不是什么大事。 我通常每次刷新一个集合需要一个视图。

我最喜欢的加载方式是使用Backbone-relational 。 如果我以分层方式组织我的应用程序。 考虑一下:

 Organization model |--> Event model |--> News model |--> Comment model 

所以当用户查看一个organization我可以加载该组织的eventsnews 。 而当用户正在查看news文章时,我急于加载该文章的comments

骨干关系为从服务器查询相关logging提供了一个很好的界面。

太多的东西使自己太多…

骨干关系也在这里帮助! 骨干关系提供了一个全球性的logging存储,certificate是非常有用的。 这样,您可以传递ID并在别处检索相同的模型。 如果你在一个地方更新它,在另一个地方可用。

a_model_instance = Model.findOrCreate({id: 1})

另一个工具是Backbone.ModelBinder 。 Backbone.ModelBinder让你build立你的模板,忘记附加查看更改。 因此,在收集信息并将其显示在标题中的示例中,只需告诉Backbone.ModelBinder观察这些元素的两个,然后根据inputchange ,将更新模型并更新您查看的模型change ,现在标题将会被更新。

它的子视图一路下来…子视图变成僵尸般的或不回应…

我真的很喜欢Backbone.Marionette 。 它为您处理了很多清理工作,并添加了一个onShowcallback,在从DOM中暂时删除视图时可能会有用。

这也有助于附加jQuery插件。 onShow方法在视图呈现并添加到DOM之后调用,以便jQuery插件代码可以正常工作。

它还提供了一些很酷的视图模板,如CollectionView ,可以很好地pipe理一个集合及其子视图。

闪烁

不幸的是,我没有太多的经验,但你也可以尝试预加载图像。 渲染他们在一个隐藏的视图,然后把他们前进。