【问题标题】:Nested Models in Backbone.js, how to approachBackbone.js 中的嵌套模型,如何处理
【发布时间】:2011-09-26 00:42:23
【问题描述】:

我从服务器获得了以下 JSON。有了这个,我想创建一个带有嵌套模型的模型。我不确定这是实现这一目标的方法。

//json
[{
    name : "example",
    layout : {
        x : 100,
        y : 100,
    }
}]

我希望将它们转换为具有以下结构的两个嵌套主干模型:

// structure
Image
    Layout
...

所以我这样定义布局模型:

var Layout = Backbone.Model.extend({});

但是我应该使用以下两种(如果有的话)技术中的哪一种来定义图像模型?下面是A还是B?

一个

var Image = Backbone.Model.extend({
    initialize: function() {
        this.set({ 'layout' : new Layout(this.get('layout')) })
    }
});

或者, B

var Image = Backbone.Model.extend({
    initialize: function() {
        this.layout = new Layout( this.get('layout') );
    }
});

【问题讨论】:

    标签: javascript backbone.js backbone-model


    【解决方案1】:

    我发布此代码作为 Peter Lyon 建议重新定义解析的示例。我有同样的问题,这对我有用(使用 Rails 后端)。这段代码是用 Coffeescript 编写的。我为不熟悉它的人做了一些明确的说明。

    class AppName.Collections.PostsCollection extends Backbone.Collection
      model: AppName.Models.Post
    
      url: '/posts'
    
      ...
    
      # parse: redefined to allow for nested models
      parse: (response) ->  # function definition
         # convert each comment attribute into a CommentsCollection
        if _.isArray response
          _.each response, (obj) ->
            obj.comments = new AppName.Collections.CommentsCollection obj.comments
        else
          response.comments = new AppName.Collections.CommentsCollection response.comments
    
        return response
    

    或者,在 JS 中

    parse: function(response) {
      if (_.isArray(response)) {
        return _.each(response, function(obj) {
          return obj.comments = new AppName.Collections.CommentsCollection(obj.comments);
        });
      } else {
        response.comments = new AppName.Collections.CommentsCollection(response.comments);
      }
      return response;
    };
    

    【讨论】:

    • Props 示例代码并建议覆盖解析。谢谢!
    • 如果能在真正的 JS 中得到你的答案会很高兴
    • 很高兴有咖啡脚本版本,谢谢。对于其他人,请尝试js2coffee.org
    • 如果问题是真正的JS,答案也应该是。
    【解决方案2】:

    我在编写 Backbone 应用程序时遇到了同样的问题。必须处理嵌入式/嵌套模型。我做了一些调整,我认为这是一个非常优雅的解决方案。

    是的,您可以修改 parse 方法以更改对象中的属性,但所有这些实际上都是 IMO 难以维护的代码,感觉更像是一种 hack 而不是解决方案。

    以下是我对您的示例的建议:

    首先像这样定义你的布局模型。

    var layoutModel = Backbone.Model.extend({});
    

    那么这是你的图像模型:

    var imageModel = Backbone.Model.extend({
    
        model: {
            layout: layoutModel,
        },
    
        parse: function(response){
            for(var key in this.model)
            {
                var embeddedClass = this.model[key];
                var embeddedData = response[key];
                response[key] = new embeddedClass(embeddedData, {parse:true});
            }
            return response;
        }
    });
    

    请注意,我没有篡改模型本身,而只是从 parse 方法传回所需的对象。

    当您从服务器读取数据时,这应该可以确保嵌套模型的结构。现在,您会注意到这里实际上没有处理保存或设置,因为我觉得使用正确的模型显式设置嵌套模型对您来说是有意义的。

    像这样:

    image.set({layout : new Layout({x: 100, y: 100})})
    

    另请注意,您实际上是通过调用嵌套模型中的 parse 方法调用:

    new embeddedClass(embeddedData, {parse:true});
    

    您可以根据需要在model 字段中定义任意数量的嵌套模型。

    当然,如果您想将嵌套模型保存在自己的表中。这还不够。但是在读取和保存整个对象的情况下,这个解决方案应该足够了。

    【讨论】:

    • 这很好......应该是公认的答案,因为它比其他方法更干净。我唯一的建议是将扩展 Backbone.Model 的类的第一个字母大写以提高可读性......即 ImageModel 和 LayoutModel
    • @StephenHandley 感谢您的评论和建议。对于信息,我实际上是在 requireJS 的上下文中使用它。因此,为了回答大写问题,var 'imageModel' 实际上返回到 requireJS。并且对模型的引用将被以下构造封装:define(['modelFile'], function(MyModel){... do something with MyModel}) 但你是对的。我确实养成了按照您建议的约定引用模型的习惯。
    • @BobS 抱歉,打错了。应该是回应。我已经解决了,谢谢指出。
    • 不错!我建议将此添加到Backbone.Model.prototype.parse 函数中。然后,您的模型所要做的就是定义子模型对象类型(在您的“模型”属性中)。
    • 酷!我最终做了类似的事情(特别是在我找到这个答案之后很遗憾)并在这里写下来:blog.untrod.com/2013/08/declarative-approach-to-nesting.html 最大的区别在于,对于深度嵌套的模型,我在根/父模型中一次声明整个映射,并且代码从那里获取并遍历整个模型,将相关对象水合到 Backbone 集合和模型中。但实际上是一种非常相似的方法。
    【解决方案3】:

    我们也有这个问题,团队工作人员实现了一个名为骨干嵌套属性的插件。

    用法很简单。示例:

    var Tree = Backbone.Model.extend({
      relations: [
        {
          key: 'fruits',
          relatedModel: function () { return Fruit }
        }
      ]
    })
    
    var Fruit = Backbone.Model.extend({
    })
    

    这样,树模型就可以访问水果了:

    tree.get('fruits')
    

    您可以在此处查看更多信息:

    https://github.com/dtmtec/backbone-nested-attributes

    【讨论】:

      【解决方案4】:

      使用主干形式

      它支持嵌套表单、模型和 toJSON。全部嵌套

      var Address = Backbone.Model.extend({
          schema: {
          street:  'Text'
          },
      
          defaults: {
          street: "Arteaga"
          }
      
      });
      
      
      var User = Backbone.Model.extend({
          schema: {
          title:      { type: 'Select', options: ['Mr', 'Mrs', 'Ms'] },
          name:       'Text',
          email:      { validators: ['required', 'email'] },
          birthday:   'Date',
          password:   'Password',
          address:    { type: 'NestedModel', model: Address },
          notes:      { type: 'List', itemType: 'Text' }
          },
      
          constructor: function(){
          Backbone.Model.apply(this, arguments);
          },
      
          defaults: {
          email: "x@x.com"
          }
      });
      
      var user = new User();
      
      user.set({address: {street: "my other street"}});
      
      console.log(user.toJSON()["address"]["street"])
      //=> my other street
      
      var form = new Backbone.Form({
          model: user
      }).render();
      
      $('body').append(form.el);
      

      【讨论】:

        【解决方案5】:

        我意识到我参加这个聚会迟到了,但我们最近发布了一个插件来处理这种情况。它叫做backbone-nestify

        所以你的嵌套模型保持不变:

        var Layout = Backbone.Model.extend({...});

        然后在定义包含模型时使用插件(使用Underscore.extend):

        var spec = {
            layout: Layout
        };
        var Image = Backbone.Model.extend(_.extend({
            // ...
        }, nestify(spec));
        

        之后,假设您有一个模型m,它是Image 的一个实例,并且您已经从m 的问题中设置了JSON,您可以这样做:

        m.get("layout");    //returns the nested instance of Layout
        m.get("layout|x");  //returns 100
        m.set("layout|x", 50);
        m.get("layout|x");  //returns 50
        

        【讨论】:

          【解决方案6】:

          如果您不想再添加另一个框架,您可以考虑创建一个基类并重写 settoJSON 并像这样使用它:

          // 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
          

          您需要BaseModel from this answer(如果您愿意,可以使用as a gist)。

          【讨论】:

            【解决方案7】:

            rycfung 的的CoffeeScript 版本的漂亮答案:

            class ImageModel extends Backbone.Model
              model: {
                  layout: LayoutModel
              }
            
              parse: (response) =>
                for propName,propModel of @model
                  response[propName] = new propModel( response[propName], {parse:true, parentModel:this} )
            
                return response
            

            不是很甜吗? ;)

            【讨论】:

            • 我的 JavaScript 中不加糖 :)
            【解决方案8】:

            我遇到了同样的问题,我一直在尝试使用 rycfung's answer 中的代码,这是一个很好的建议。
            但是,如果您不想直接set 嵌套模型,或者不想不断 在options 中传递{parse: true},另一种方法是重新定义set 本身。

            Backbone 1.0.0中,setconstructorunsetclearfetchsave 中被调用。

            对于所有需要嵌套模型和/或集合的模型,请考虑以下超级模型

            /** Compound supermodel */
            var CompoundModel = Backbone.Model.extend({
                /** Override with: key = attribute, value = Model / Collection */
                model: {},
            
                /** Override default setter, to create nested models. */
                set: function(key, val, options) {
                    var attrs, prev;
                    if (key == null) { return this; }
            
                    // Handle both `"key", value` and `{key: value}` -style arguments.
                    if (typeof key === 'object') {
                        attrs = key;
                        options = val;
                    } else {
                        (attrs = {})[key] = val;
                    }
            
                    // Run validation.
                    if (options) { options.validate = true; }
                    else { options = { validate: true }; }
            
                    // For each `set` attribute, apply the respective nested model.
                    if (!options.unset) {
                        for (key in attrs) {
                            if (key in this.model) {
                                if (!(attrs[key] instanceof this.model[key])) {
                                    attrs[key] = new this.model[key](attrs[key]);
                                }
                            }
                        }
                    }
            
                    Backbone.Model.prototype.set.call(this, attrs, options);
            
                    if (!(attrs = this.changedAttributes())) { return this; }
            
                    // Bind new nested models and unbind previous nested models.
                    for (key in attrs) {
                        if (key in this.model) {
                            if (prev = this.previous(key)) {
                                this._unsetModel(key, prev);
                            }
                            if (!options.unset) {
                                this._setModel(key, attrs[key]);
                            }
                        }
                    }
                    return this;
                },
            
                /** Callback for `set` nested models.
                 *  Receives:
                 *      (String) key: the key on which the model is `set`.
                 *      (Object) model: the `set` nested model.
                 */
                _setModel: function (key, model) {},
            
                /** Callback for `unset` nested models.
                 *  Receives:
                 *      (String) key: the key on which the model is `unset`.
                 *      (Object) model: the `unset` nested model.
                 */
                _unsetModel: function (key, model) {}
            });
            

            请注意,model_setModel_unsetModel 故意留空。在这个抽象级别,您可能无法为回调定义任何合理的操作。但是,您可能希望在扩展 CompoundModel 的子模型中覆盖它们。
            这些回调很有用,例如,绑定侦听器和传播 change 事件。


            示例:

            var Layout = Backbone.Model.extend({});
            
            var Image = CompoundModel.extend({
                defaults: function () {
                    return {
                        name: "example",
                        layout: { x: 0, y: 0 }
                    };
                },
            
                /** We need to override this, to define the nested model. */
                model: { layout: Layout },
            
                initialize: function () {
                    _.bindAll(this, "_propagateChange");
                },
            
                /** Callback to propagate "change" events. */
                _propagateChange: function () {
                    this.trigger("change:layout", this, this.get("layout"), null);
                    this.trigger("change", this, null);
                },
            
                /** We override this callback to bind the listener.
                 *  This is called when a Layout is set.
                 */
                _setModel: function (key, model) {
                    if (key !== "layout") { return false; }
                    this.listenTo(model, "change", this._propagateChange);
                },
            
                /** We override this callback to unbind the listener.
                 *  This is called when a Layout is unset, or overwritten.
                 */
                _unsetModel: function (key, model) {
                    if (key !== "layout") { return false; }
                    this.stopListening();
                }
            });
            

            这样,您就可以自动创建嵌套模型和传播事件。还提供并测试了示例用法:

            function logStringified (obj) {
                console.log(JSON.stringify(obj));
            }
            
            // Create an image with the default attributes.
            // Note that a Layout model is created too,
            // since we have a default value for "layout".
            var img = new Image();
            logStringified(img);
            
            // Log the image everytime a "change" is fired.
            img.on("change", logStringified);
            
            // Creates the nested model with the given attributes.
            img.set("layout", { x: 100, y: 100 });
            
            // Writing on the layout propagates "change" to the image.
            // This makes the image also fire a "change", because of `_propagateChange`.
            img.get("layout").set("x", 50);
            
            // You may also set model instances yourself.
            img.set("layout", new Layout({ x: 100, y: 100 }));
            

            输出:

            {"name":"example","layout":{"x":0,"y":0}}
            {"name":"example","layout":{"x":100,"y":100}}
            {"name":"example","layout":{"x":50,"y":100}}
            {"name":"example","layout":{"x":100,"y":100}}
            

            【讨论】:

              【解决方案9】:

              如果你想保持简单,我会选择选项 B。

              另一个不错的选择是使用Backbone-Relational。您只需定义如下内容:

              var Image = Backbone.Model.extend({
                  relations: [
                      {
                          type: Backbone.HasOne,
                          key: 'layout',
                          relatedModel: 'Layout'
                      }
                  ]
              });
              

              【讨论】:

              • +1 Backbone-Releational 似乎相当成熟:自己的网站、1.6k 星、200 多个分叉。
              【解决方案10】:

              我将 Backbone DeepModel 插件用于嵌套模型和属性。

              https://github.com/powmedia/backbone-deep-model

              您可以绑定以更改事件'n 级别的深度。例如: model.on('change:example.nestedmodel.attribute', this.myFunction);

              【讨论】:

                【解决方案11】:

                使用来自Backbone-associationsBackbone.AssociatedModel

                    var Layout = Backbone.AssociatedModel.extend({
                        defaults : {
                            x : 0,
                            y : 0
                        }
                    });
                    var Image = Backbone.AssociatedModel.extend({
                        relations : [
                            type: Backbone.One,
                            key : 'layout',
                            relatedModel : Layout          
                        ],
                        defaults : {
                            name : '',
                            layout : null
                        }
                    });
                

                【讨论】:

                【解决方案12】:

                我不确定 Backbone 本身是否有推荐的方法来执行此操作。 Layout 对象在后端数据库中是否有自己的 ID 和记录?如果是这样,您可以将其作为自己的模型。如果没有,您可以将其保留为嵌套文档,只需确保在 saveparse 方法中正确地将其转换为 JSON 和从 JSON 转换即可。如果您最终采取了这样的方法,我认为您的 A 示例与骨干网更一致,因为set 将正确更新attributes,但我再次不确定骨干网是做什么用的默认情况下嵌套模型。您可能需要一些自定义代码来处理此问题。

                【讨论】:

                • 啊!抱歉,它缺少 new 运算符。我已经对其进行了编辑以修复此错误。
                • 哦,那我误解了你的问题。我会更新我的答案。
                猜你喜欢
                • 2023-03-13
                • 1970-01-01
                • 1970-01-01
                • 2020-07-05
                • 2013-10-04
                • 2011-03-09
                • 2012-02-18
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多