javascript - BackboneJS渲染问题

标签 javascript jquery node.js backbone.js coffeescript

在过去的六个月中,我一直在与Backbone合作。前两个月很困惑,学习并弄清楚我要如何围绕它构建代码。接下来的4个月,该公司推出了量身定制的应用程序。不要误会我的意思,Backbone使我摆脱了以前标准的成千上万行客户端代码的困惑,但是它使我能够在更短的时间内完成更多宏伟的事情,从而带来了一系列全新的问题。对于我在这里提出的所有问题,有一些简单的解决方案,它们就像骇客一样,或者只是觉得不对劲。我 promise 提供300分的赏金,以提供出色的解决方案。开始:

  • 加载-对于我们的用例(管理面板),悲观同步是不好的。对于某些事情,我需要先在服务器上验证它们,然后再接受它们。我们是在'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")
    

    伟大的。它按预期工作,但感觉不正确。我们将此事件绑定(bind)到所有相关 View ,并显示一个加载图标,直到从该模型收到成功或错误事件为止。有没有更好,更精明的方法来做到这一点?

    现在为困难的人:
  • 太多的事情使自己变得太多-假设我们的应用程序具有选项卡。每个选项卡控制一个集合。在左侧,您可以获取收藏集。您单击一个模型以开始在中心对其进行编辑。您更改其名称,然后按Tab键转到下一个表单项。现在,您的应用程序是一个“实时的东西”,它可以注意到差异,运行验证并自动将更改同步到服务器,而无需保存按钮!很好,但是表格开头的H2与输入中的名称相同-您需要对其进行更新。哦,您需要将列表上的名称更新到侧面。哦,列表按名称排序!

  • 这是另一个示例:您要在集合中创建一个新项目。您按下“新建”按钮,然后开始填写表格。您立即将项目添加到集合中吗?但是,如果您决定丢弃它,会发生什么?或者,如果您将整个收藏保存在另一个选项卡上?并且,有一个文件上传-您需要先保存并同步模型,然后才能开始上传文件(以便可以将文件附加到模型)。因此,一切开始震撼地呈现:保存模型和列表,然后表单再次呈现自己-现在已同步,因此您获得了一个新的Delete按钮,并在列表中显示-但是现在文件上传完成了上传,因此一切重新开始渲染。

    将 subview 添加到混合中,一切开始看起来像Fellini电影。
  • 一直到 subview -Here's a good article about this stuff。对于所有神圣的事物,我无法找到一种将jQuery插件或DOM事件附加到具有 subview 的任何 View 的正确方法。 hell 随之而来。工具提示会听到渲染很长一段时间并开始变乱, subview 变得像僵尸一样或没有响应。这是实际错误所在的主要痛点,但是我仍然没有一个全面的解决方案。
  • 闪烁-渲染速度很快。实际上,它是如此之快,以至于我的屏幕看起来好像被卡住了。有时是必须重新加载图像(通过另一个服务器调用!),因此html最小化,然后突然再次最大化-该元素的css width + height将解决此问题。有时我们可以使用fadeIn和fadeOut解决此问题-这在编写时很麻烦,因为有时我们会重用 View ,有时会重新创建 View 。

  • TL; DR -我在Backbone中的 View 和 subview 存在问题-它渲染太多次,渲染时闪烁, subview 使我的DOM事件分离并吞噬了我的大脑。

    谢谢!

    更多详细信息:BackboneJS与Ruby on Rails Gem。使用UnderscoreJS模板的模板。

    最佳答案

    View 的局部渲染

    为了最大程度地减少DOM层次结构的完整呈现,您可以在DOM中设置特殊的节点,以反射(reflect)给定属性的更新。

    让我们使用这个简单的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,这将是我们的注入(inject)点。

    然后,我们可以定义一个基本 View (或修改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/一起玩的 fiddle

    限制DOM操作

    将渲染委派给 subview 可能会花费很多,它们的HTML片段必须插入到父 View 的DOM中。
    看看这个jsperf test comparing different methods of rendering

    其要点是生成完整的HTML结构然后应用 View 比构建 View 和 subview 然后级联呈现要快得多。例如,

    <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,要么分离该节点。

    压扁僵尸

    德里克·贝利(Derick Bailey)写了一篇关于eradicating zombie views的出色文章

    基本上,您必须记住,丢弃 View 时,必须取消绑定(bind)所有监听器并执行任何其他清除操作,例如销毁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/

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

    注意:对JS代码感到抱歉,我对Coffeescript不够熟悉,无法提供准确的摘要。

    关于javascript - BackboneJS渲染问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12004534/

    相关文章:

    javascript - PHP 页面上未加载 Highcharts

    javascript - 如何避免连续双击?

    FF5 扩展/插件中的 JQuery 跨域 JSONP,未定义回调

    javascript - 调试node.js时出现"breakpoint set but not yet bound"错误

    javascript - "Uncaught TypeError: Cannot read property ' 在 angularJS/NodeJS 中使用 strip 支付方法 Stripe.charges.create({}) 时创建 ' of undefined"

    javascript - XMLHttpRequests 上是否有可用的 NavigationTiming 接口(interface)?

    javascript - 在 Angular Controller 中访问表情符号的正确方法

    javascript - 从另一个选择框中隐藏所选选项

    javascript - jquery 使用多个 setTimeouts

    node.js - NodeJs 产生 ENOENT 错误(Raspbian)