【问题标题】:Save several Backbone models at once一次保存多个 Backbone 模型
【发布时间】:2012-09-05 18:11:15
【问题描述】:

我有一个包含大量模型的 Backbone 集合。

只要在模型上设置特定属性并保存它,就会触发大量计算并重新呈现 UI。

但是,我希望能够一次在多个模型上设置属性,并且只有在它们全部设置后才进行保存和重新渲染。我当然不想一次操作多次http请求,绝对不想重绘界面十次。

我希望在 Backbone.Collection 上找到一个保存方法,它可以计算出哪些模型 hasChanged(),将它们作为 json 组合在一起并发送到后端。然后可以由集合上的事件触发重新呈现。没有这样的运气。

这似乎是一个很常见的要求,所以我想知道为什么 Backbone 没有实现。这是否违背了 RESTful 架构,将多个内容保存到单个端点?如果是这样,那又如何?发出 1000 个请求来持久化 1000 个小项目是不切实际的。

那么,使用我自己的 save 方法来增加 Backbone.Collection 的唯一解决方案是迭代其所有模型并为所有已更改的模型构建 json 并将其发送到后端?还是有人有更简洁的解决方案(或者我只是错过了什么!)?

【问题讨论】:

标签: javascript backbone.js


【解决方案1】:

我最终用几种方法来扩充 Backbone.Collection 来处理这个问题。

saveChangeMethod 创建一个要传递给 Backbone.sync 的虚拟模型。模型需要的所有主干同步方法是它的 url 属性和 toJSON 方法,所以我们可以轻松地把它搞定。

在内部,模型的 toJSON 方法只返回其属性的副本(发送到服务器),因此我们可以愉快地使用只返回模型数组的 toJSON 方法。 Backbone.sync 对此进行了字符串化处理,只为我们提供了属性数据。

成功时,saveChanged 会触发要处理的集合上的事件一次。已经编写了一些代码,让它为任何批次模型中发生变化的每个属性触发一次特定事件。

Backbone.Collection.prototype.saveChanged = function () {
    var me = this,
        changed = me.getChanged(),
        dummy = {
            url: this.url,
            toJSON: function () {
                return changed.models;
            }
        },
        options = {
            success: function (model, resp, xhr) {
                for (var i = 0; i < changed.models.length; i++) {
                    changed.models[i].chnageSilently();
                }
                for (var attr in changed.attributes) {
                    me.trigger("batchchange:" + attr);
                }
                me.trigger("batchsync", changed);
            }
        };
    return Backbone.sync("update", dummy, options);
}

然后我们只需要集合上的 getChanged() 方法。这将返回一个具有 2 个属性的对象、一个已更改模型的数组和一个标记哪些属性已更改的对象:

Backbone.Collection.prototype.getChanged = function () {
    var models = [],
        changedAttributes = {};
    for (var i = 0; i < this.models.length; i++) {
        if (this.models[i].hasChanged()) {
            _.extend(changedAttributes, this.models[i].changedAttributes());
            models.push(this.models[i]);
        }
    }
    return models.length ? {models: models, attributes: changedAttributes} : null;
}

虽然这是对主干“更改模型”范式的预期用途的轻微滥用,但批处理的全部意义在于,当模型更改时,我们不希望发生任何事情(即触发任何事件)。

因此,我们必须将 {silent: true} 传递给模型的 set() 方法,因此使用主干的 hasChanged() 来标记等待保存的模型是有意义的。当然,如果您出于其他目的默默地更改模型,这将是有问题的——collection.saveChanged() 也会保存这些,因此值得考虑设置一个替代标志。

无论如何,如果我们这样做,保存时,我们需要确保主干现在认为模型没有更改(不触发它们的更改事件),因此我们需要手动操作模型,就好像它没有改变。 saveChanged() 方法迭代我们更改的模型,并在模型上调用这个 changeSilently() 方法,这基本上只是 Backbone 的 model.change() 方法,没有触发器:

Backbone.Model.prototype.changeSilently = function () {
    var options = {},
    changing = this._changing;
    this._changing = true;
    for (var attr in this._silent) this._pending[attr] = true;
    this._silent = {};
    if (changing) return this;

    while (!_.isEmpty(this._pending)) {
        this._pending = {};
        for (var attr in this.changed) {
        if (this._pending[attr] || this._silent[attr]) continue;
        delete this.changed[attr];
        }
        this._previousAttributes = _.clone(this.attributes);
    }
    this._changing = false;
    return this;
}

用法:

model1.set({key: value}, {silent: true});
model2.set({key: value}, {silent: true});
model3.set({key: value}, {silent: true});
collection.saveChanged();

重新。 RESTful .. 对集合的端点执行 PUT 以更改其“某些”记录是不太正确的。从技术上讲,PUT 应该替换整个集合,但在我的应用程序真正需要替换整个集合之前,我很乐意采用务实的方法。

【讨论】:

    【解决方案2】:

    你可以定义一个新的资源来完成这种行为,你可以称之为MyModelBatch

    您需要在服务器端实现一个新资源,该资源能够消化 Array 的模型并执行正确的操作:CREATEUPDATEDESTROY

    您还需要在您的 Backbone 客户端实现一个 Model,其中一个 属性 是模型数组和一个特殊的 url,它不使用 id

    关于重新渲染的事情,我建议你尝试让每个模型都有一个视图,这样在模型发生变化时会有尽可能多的渲染,但它们会详细重新渲染没有重复。

    【讨论】:

    • 谢谢。根据您使用临时模型的建议,我注意到 Backbone.sync 只查看模型 url 属性和 toJSON 方法,因此我们可以轻松地伪造一个用于同步的模型,而不是使用实际模型。 toJSON 仅返回要字符串化的模型属性的副本,因此我们可以使用它来返回我们的模型数组。看我的回答。
    【解决方案3】:

    这是我想出来的。

    Backbone.Collection.extend({
        saveAll: function(models, key, val, options) {
    
            var attrs, xhr, wait, that = this;
    
            var transport = {
                url: this.url,
                models: [],
                toJSON: function () {
                    return { models: this.models };
                },
                trigger: function(){
                    return that.trigger.apply(that, arguments);
                }
            };
    
            if(models == null){
                models = this.models;
            }
    
            // Handle both `"key", value` and `{key: value}` -style arguments.
            if (key == null || typeof key === 'object') {
                attrs = key;
                options = val;
            } else {
                (attrs = {})[key] = val;
            }
    
            options = _.extend({validate: true}, options);
            wait = options.wait;
    
            // After a successful server-side save, the client is (optionally)
            // updated with the server-side state.
            if (options.parse === void 0) options.parse = true;
    
            var triggers = [];
    
            _.each(models, function(model){
    
                var attributes = model.attributes;
    
                // If we're not waiting and attributes exist, save acts as
                // `set(attr).save(null, opts)` with validation. Otherwise, check if
                // the model will be valid when the attributes, if any, are set.
                if (attrs && !wait) {
                    if (!model.set(attrs, options)) return false;
                } else {
                    if (!model._validate(attrs, options)) return false;
                }
    
                // Set temporary attributes if `{wait: true}`.
                if (attrs && wait) {
                    model.attributes = _.extend({}, attributes, attrs);
                }
    
                transport.models.push(model.toJSON());
    
                triggers.push(function(resp){
                    if(resp.errors){
                        model.trigger('error', model, resp, options);
                    } else {
                        // Ensure attributes are restored during synchronous saves.
                        model.attributes = attributes;
                        var serverAttrs = options.parse ? model.parse(resp, options) : resp;
                        if (wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
                        if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
                            return false;
                        }
                        model.trigger('sync', model, resp, options);
                    }
                });
    
                // Restore attributes.
                if (attrs && wait) model.attributes = attributes;
            });
    
            var success = options.success;
            options.success = function(resp) {
                _.each(triggers, function(trigger, i){
                    trigger.call(options.context, resp[i]);
                });
                if (success) success.call(options.context, models, resp, options);
            };
            return this.sync('create', transport, options);
        }
    });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-25
      • 1970-01-01
      • 2015-02-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多