【问题标题】:Better solution for nested Backbone.js collections嵌套 Backbone.js 集合的更好解决方案
【发布时间】:2012-05-11 16:08:51
【问题描述】:

我的许多 Backbone 模型经常处理嵌套模型和集合,到目前为止,我手动使用 defaultsparsetoJSON 的组合来实现嵌套:

ACME.Supplier = Backbone.Model.extend({
    defaults: function() {
        return {
            contacts: new ACME.Contacts(),
            tags: new ACME.Tags(),
            attachments: new ACME.Attachments()
        };
    },

    parse: function(res) {
        if (res.contacts) res.contacts = new ACME.Contacts(res.contacts);
        if (res.tags) res.tags = new ACME.Tags(res.tags);
        if (res.attachments) res.attachments = new ACME.Attachments(res.attachments);

        return res;
    }
});

ACME.Tag = Backbone.Model.extend({
    toJSON: function() {
        return _.pick(this.attributes, 'id', 'name', 'type');
    }
});

我查看了一些插件,它们基本上与上面的功能相同,但控制更少,样板更多,所以我想知道是否有人对这个常见的 Backbone.js 问题有更优雅的解决方案。


编辑:我最终采用了以下方法:

ACME.Supplier = Backbone.Model.extend({
    initialize: function(options) {
        this.tags = new ACME.Tags(options.tags);
    },

    parse: function(res) {
        res.tags && this.tags.reset(res.tags);

        return res;
    }
});

ACME.Tag = Backbone.Model.extend({
    toJSON: function() {
        return _.pick(this.attributes, 'id', 'name', 'type');
    }
});

值得注意的是,后来我发现你需要通过options对象将嵌套模型/集合数据从构造函数传递到嵌套模型的构造函数中。

【问题讨论】:

    标签: backbone.js nested-attributes


    【解决方案1】:

    我认为你的方法没有任何问题。

    恕我直言,Model.parse() 方法如果:在需要特殊解析行为的情况下被覆盖

    我唯一会改变的想法是这样的:

    if (res.tags) res.tags = new ACME.Tags(res.tags);
    

    为此:

    if (res.tags) this.tags.reset(res.tags);
    

    由于您已经有一个 ACME.Tags 集合的实例,我会重用它。

    另外,我不太喜欢 defaults 实现,我习惯于在 Model.initialize() 中执行此初始化,但我认为这是个人喜好问题。

    【讨论】:

    • 我相信这种方法是不借助插件或类似工具的唯一方法,谢谢。
    【解决方案2】:

    我发现使用这种方法时,Supplier 的 toJSON 函数会过时,因此最好从它的 JSON 状态和子数据中重新组装回它的 JSON 状态。

    ACME.Supplier = Backbone.Model.extend({
        initialize: function(options) {
            this.tags = new ACME.Tags(options.tags);
        },
    
        parse: function(res) {
            res.tags && this.tags.reset(res.tags);
    
            return res;
        },
    
        toJSON: function({
            return _.extend(
                _.pick(this.attributes, 'id', 'attr1', 'attr2'), {
                tags: this.tags.toJSON(),
            });
        })
    

    });

    【讨论】:

      【解决方案3】:

      我们不想添加另一个框架来实现这一点,所以我们将它抽象到一个基本模型类中。 以下是您声明和使用它的方式 (available as a gist):

      // Declaration
      
      window.app.viewer.Model.GallerySection = window.app.Model.BaseModel.extend({
        nestedTypes: {
          background: window.app.viewer.Model.Image,
          images: window.app.viewer.Collection.MediaCollection
        }
      });
      
      // Usage
      
      var gallery = new window.app.viewer.Model.GallerySection({
          background: { url: 'http://example.com/example.jpg' },
          images: [
              { url: 'http://example.com/1.jpg' },
              { url: 'http://example.com/2.jpg' },
              { url: 'http://example.com/3.jpg' }
          ],
          title: 'Wow'
      }); // (fetch will work equally well)
      
      console.log(gallery.get('background')); // window.app.viewer.Model.Image
      console.log(gallery.get('images')); // window.app.viewer.Collection.MediaCollection
      console.log(gallery.get('title')); // plain string
      

      它同样适用于 settoJSON
      这是BaseModel

      window.app.Model.BaseModel = Backbone.Model.extend({
        constructor: function () {
          if (this.nestedTypes) {
            this.checkNestedTypes();
          }
      
          Backbone.Model.apply(this, arguments);
        },
      
        set: function (key, val, options) {
          var attrs;
      
          /* jshint -W116 */
          /* jshint -W030 */
          // Code below taken from Backbone 1.0 to allow different parameter styles
          if (key == null) return this;
          if (typeof key === 'object') {
            attrs = key;
            options = val;
          } else {
            (attrs = {})[key] = val;
          }
          options || (options = {});
          // Code above taken from Backbone 1.0 to allow different parameter styles
          /* jshint +W116 */
          /* jshint +W030 */
      
          // What we're trying to do here is to instantiate Backbone models and collections
          // with types defined in this.nestedTypes, and use them instead of plain objects in attrs.
      
          if (this.nestedTypes) {
            attrs = this.mapAttributes(attrs, this.deserializeAttribute);
          }
      
          return Backbone.Model.prototype.set.call(this, attrs, options);
        },
      
        toJSON: function () {
          var json = Backbone.Model.prototype.toJSON.apply(this, arguments);
      
          if (this.nestedTypes) {
            json = this.mapAttributes(json, this.serializeAttribute);
          }
      
          return json;
        },
      
        mapAttributes: function (attrs, transform) {
          transform = _.bind(transform, this);
          var result = {};
      
          _.each(attrs, function (val, key) {
            result[key] = transform(val, key);
          }, this);
      
          return result;
        },
      
        serializeAttribute: function (val, key) {
          var NestedType = this.nestedTypes[key];
          if (!NestedType) {
            return val;
          }
      
          if (_.isNull(val) || _.isUndefined(val)) {
            return val;
          }
      
          return val.toJSON();
        },
      
        deserializeAttribute: function (val, key) {
          var NestedType = this.nestedTypes[key];
          if (!NestedType) {
            return val;
          }
      
          var isCollection = this.isTypeASubtypeOf(NestedType, Backbone.Collection),
              child;
      
          if (val instanceof Backbone.Model || val instanceof Backbone.Collection) {
            child = val;
          } else if (!isCollection && (_.isNull(val) || _.isUndefined(val))) {
            child = null;
          } else {
            child = new NestedType(val);
          }
      
          var prevChild = this.get(key);
      
          // Return existing model if it is equal to child's attributes
      
          if (!isCollection && child && prevChild && _.isEqual(prevChild.attributes, child.attributes)) {
            return prevChild;
          }
      
          return child;
        },
      
        isTypeASubtypeOf: function (DerivedType, BaseType) {
          // Go up the tree, using Backbone's __super__.
          // This is not exactly encouraged by the docs, but I found no other way.
      
          if (_.isUndefined(DerivedType['__super__'])) {
            return false;
          }
      
          var ParentType = DerivedType['__super__'].constructor;
          if (ParentType === BaseType) {
            return true;
          }
      
          return this.isTypeASubtypeOf(ParentType, BaseType);
        },
      
        checkNestedTypes: function () {
          _.each(this.nestedTypes, function (val, key) {
            if (!_.isFunction(val)) {
              console.log('Not a function:', val);
              throw new Error('Invalid nestedTypes declaration for key ' + key + ': expected a function');
            }
          });
        },
      }
      

      【讨论】:

        【解决方案4】:

        面对同样的问题,我做了类似的事情(下面的代码是TypeScript编译器的输出,所以有点冗长):

          var Model = (function (_super) {
            __extends(Model, _super);
            function Model() {
                _super.apply(this, arguments);
            }
            Model.prototype.fieldToType = function () {
                return {};
            };
        
            Model.prototype.parse = function (response, options) {
                _.each(this.fieldToType(), function (type, field) {
                    if (response[field]) {
                        if (_.isArray(response[field])) {
                            response[field] = _.map(response[field], function (value) {
                                return new type(value, { parse: true });
                            });
                        } else {
                            response[field] = new type(response[field], { parse: true });
                        }
                    }
                });
                return _super.prototype.parse.call(this, response, options);
            };
            Model.prototype.toJSON = function () {
                var j = _super.prototype.toJSON.call(this);
                _.each(this.fieldToType(), function (type, field) {
                    if (j[field]) {
                        if (_.isArray(j[field])) {
                            j[field] = _.map(j[field], function (value) {
                                return value.toJSON();
                            });
                        } else {
                            j[field] = j[field].toJSON();
                        }
                    }
                });
                return j;
            };
            return Model;
        })(Backbone.Model);
        

        然后我可以简单地重写 fieldToType 方法来定义我的字段类型:

        PendingAssignmentOffer.prototype.fieldToType = function () {
            return {
                'creator': User,
                'task_templates': TaskTemplateModel,
                'users': User,
                'school_classes': SchoolClass
            };
        };
        

        【讨论】:

          猜你喜欢
          • 2014-02-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-05-12
          • 1970-01-01
          • 2011-12-11
          • 1970-01-01
          相关资源
          最近更新 更多