BackboneJS渲染问题

Cam*_*mel 32 javascript jquery node.js coffeescript backbone.js

在过去的六个月里,我一直在使用Backbone.前两个月一直在搞乱,学习并弄清楚我想如何构建我的代码.接下来的4个月正在掀起一场适合生产的应用.不要误会我的意思,Backbone已经把我从以前成为标准的数千行的客户端代码中解救出来,但它使我能够在更短的时间内完成更多宏伟的事情,从而打开一堆全新的问题.对于我在这里提出的所有问题,有简单的解决方案,感觉像黑客或只是感觉不对.我保证获得300点奖励以获得一个很棒的解决方案.开始:

  1. 正在加载 - 对于我们的用例(管理面板),悲观的同步很糟糕.对于某些事情,我需要在接受它们之前验证服务器上的内容.我们在'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")
Run Code Online (Sandbox Code Playgroud)

大.它按预期工作,但感觉不正确.我们将此事件绑定到所有相关视图并显示加载图标,直到我们从该模型收到成功或错误事件.有更好,更健全的方式吗?

现在为艰难的:

  1. 太多东西让自己太多了 - 让我们说我们的应用程序有标签.每个选项卡控制一个集合.在左侧,您可以获得该系列.单击模型以开始在中心编辑它.您更改其名称并按Tab键转到下一个表单项.现在,您的应用程序是一个"实时的东西",注意到差异,运行验证,并自动将更改同步到服务器,不需要保存按钮!很好,但表单开头的H2与输入中的名称相同 - 您需要更新它.哦,你需要更新列表中的名称.哦,名单按姓名排序!

这是另一个例子:您想在集合中创建一个新项目.按"新建"按钮,即可开始填写表格.您是否立即将该项目添加到集合中?但如果您决定丢弃它会发生什么?或者如果您将整个集合保存在另一个选项卡上?并且,有一个文件上传 - 您需要先保存并同步模型,然后才能开始上传文件(这样您就可以将文件附加到模型中).所以一切都开始渲染震动:你保存模型和列表,表格再次渲染自己 - 它现在已同步,所以你得到一个新的删除按钮,它显示在列表中 - 但现在文件上传完成上传,所以一切再次开始渲染.

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

  1. 它的子视图一直向下 - 这是关于这些东西的好文章.我不能,因为对所有神圣事物的热爱,找到一种将jQuery插件或DOM事件附加到具有子视图的任何视图的正确方法.地狱随之而来.工具提示会听到渲染很长时间并且开始变得怪异,子视图变得像僵尸一样或者没有响应.这是主要的痛点,因为这里有实际的错误,但我仍然没有一个全面的解决方案.

  2. 闪烁 - 渲染速度很快.事实上,它是如此之快,我的屏幕看起来像是癫痫发作.有时它的图像必须再次加载(使用另一个服务器调用!),因此html最小化然后再次最大化 - 该元素的css宽度+高度将解决这个问题.有时候我们可以用fadeIn和fadeOut来解决这个问题 - 这是写作屁股的痛苦,因为有时我们会重复使用视图,有时会重新创建它.

TL; DR - 我在Backbone中遇到了查看和子视图的问题 - 渲染次数太多,渲染时会闪烁,子视图会分离我的DOM事件并吃掉我的大脑.

谢谢!

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

nik*_*shr 16

部分呈现视图

为了最小化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>
Run Code Online (Sandbox Code Playgroud)

注意这个课程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;
    }
});
Run Code Online (Sandbox Code Playgroud)

代码会有所不同,以适应模型而不是集合.与http://jsfiddle.net/nikoshr/cfcDX/一起玩的小提琴

限制DOM操作

将渲染委托给子视图可能成本很高,他们的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>
Run Code Online (Sandbox Code Playgroud)
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)
});
Run Code Online (Sandbox Code Playgroud)

http://jsfiddle.net/nikoshr/UeefE/

请注意,jsperf显示模板可以拆分为子模板而不会有太多损失,这将允许您为行提供部分呈现.

在相关的说明中,不要在连接到DOM的节点上工作,这将导致不必要的回流.在操作之前创建新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);
        }
    }
});
Run Code Online (Sandbox Code Playgroud)

更新我之前的示例以使行具有可拖动的行为将如下所示:

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

http://jsfiddle.net/nikoshr/yL7g6/

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

NB:抱歉JS代码,我对Coffeescript不够熟悉,无法提供准确的代码片段.


Ark*_*ady 8

好的,顺序.. :)

  1. 载入中...

如果您想验证存储在服务器上的数据,请在服务器端进行.如果服务器上的验证不成功,服务器应该不发送200个HTTP代码,因此保存Backbone.Model的metod会触发错误.

另一方面,对于验证数据骨干有未实现的验证方法.我想正确选择实施和使用它.但请记住,在设置和保存之前调用validate,如果validate返回错误,则set和save将不会继续,并且不会修改模型属性.验证失败会触发"错误"事件.

另一种方式,当我们调用silent set(使用{silent:true} param)时,我们应该手动调用isValid方法来验证数据.

  1. 太多东西让自己太多了..

您必须根据逻辑分离您的视图.收集的良好实践是每个模型的单独视图.在这种情况下,您可以独立渲染每个元素.甚至更多 - 当您对集合视图进行初始化时,您可以将集合中每个模型的任何事件绑定到适当的视图,并且它们将自动呈现.

很好,但表单开头的H2与输入中的名称相同 - 您需要更新它.哦,你需要更新列表中的名称.

你可以方法上使用JQuery 来实现发送值来显示的回调.例:

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

哦,名单按姓名排序!

您可以对主干集合使用排序方法.但是(!)调用sort会触发集合的"重置"事件.通过{silent:true}来避免这种情况.如何

这是另一个例子:你想在集合中创建一个新项目......

当我们按下"新建"按钮时,我们需要创建一个新模型,但只有当.save()方法触发成功时,我们才应该将此模型推送到集合中.在另一种情况下,我们应该显示错误消息 当然,我们没有理由在我们的集合中添加新模型,直到它被验证并保存在服务器上.

  1. 它的子视图一直向下...子视图变成僵尸般的或不响应.

当您(或任何模型)调用render方法时,将重新创建其中的所有元素.因此,如果您有子视图,则应调用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'));
  }
})
Run Code Online (Sandbox Code Playgroud)

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