从第三方填充模型 API

Populating model from a third-party API

我正在使用 Backbone 进行个人项目,我在其中创建了一个名为 MyModel 的模型。在初始化这个模型时,我想从来自第三方 API:

的 JSON 响应中填充它的属性
app.MyModel = Backbone.Model.extend({
      url: 'https://api.xxxxxx.com/v12_1/item?id=53444d0d7ba4ca15456f5690&appId=xxxx&appKey=yyyy',

      defaults: {
          name: 'Default Name'
      }

  });

此模型用于一个集合,该集合将用于嵌入另一个模型的属性:

app.MyModels = Backbone.Collection.extend({
    model: app.MyModel
});

app.MyModel2 = Backbone.Model.extend({

    // Default attributes
    defaults: {
        name: 'Default Name'
    },

    initialize: function() {
        this.myModels = new app.MyModels();
        this.myModels.on('change', this.save);
    }
});

在为 MyModel2 创建的视图中,我向全局元素添加了一个侦听器,因此我们可以初始化 MyModel 的实例并将其添加到 MyModel2 中的 MyModels

app.MyModel2View = Backbone.View.extend({

    initialize: function() {
        // ...some code...
        var self = this;
        this.$(".add-myModel").click(function() {
            var myModel = new app.MyModel();
            myModel.fetch();
            self.model.myModels.add(myModel);
        });
        // ...some code...
    },
    // ...some code...
});

这实际上是在实现预期目标,但在单击元素并添加实例时会在控制台中抛出错误:

backbone.js:646 Uncaught TypeError: this.isNew is not a function

这是 Backbone 中从外部 API 填充模型实例的正确方法吗?我正在尝试找出此错误的原因。

如果没有更完整的信息很难说,但看起来你在保存时没有正确设置上下文 MyModels:

this.myModels.on('change', this.save);

这是 on() 方法的可选最后一个参数,所以可能:

this.myModels.on('change', this.save, this);

详见documentation

,他只关注最可能的错误,而让您处理其他所有事情。我会在我的回答中尝试对此进行扩展。


模型 URL 在查询字符串中有 ID

API的URL非常复杂,每次需要的时候都复制粘贴很麻烦。最好在一个地方进行 URL 处理,实现此目的的一种方法是使用简单的 service.

// The API service to use everywhere you need the API specific data.
app.API = {
    protocol: 'https',
    domain: 'api.xxxxxx.com',
    root: '/v12_1/',
    params: {
        appId: 'xxxx',
        appKey: 'yyyy',
    },
    /**
     * Get the full API url, with your optional path.
     * @param  {String} path (optional) to add to the url.
     * @return {String}  full API url with protocol, domain, root.
     */
    url: function(path) {
        path = path || '';
        if (path.slice(-1) !== '/') path += '/';
        return this.protocol + "://" + this.domain + this.root + path;
    },
    /**
     * Adds the query string to the url, merged with the default API parameters.
     * @param  {String} url  (optional) before the query string
     * @param  {Object} params to transform into a query string
     * @return {String}   e.g.: "your-url?param=value&otherparam=123"
     */
    applyParams: function(url, params) {
        return (url || "") + "?" + $.param(_.extend({}, this.params, params));
    },
};

填写 API 信息。

然后,您可以创建基础模型和集合(或替换默认的 Backbone 行为)。

app.BaseModel = Backbone.Model.extend({
    setId: function(id, options) {
        return this.set(this.idAttribute, id, options);
    },
    url: function() {
        var base =
            _.result(this, 'urlRoot') ||
            _.result(this.collection, 'url') ||
            urlError();
        var id = this.get(this.idAttribute);
        return app.API.applyParams(base, this.isNew() || { id: encodeURIComponent(id) });
    },
});

app.BaseCollection = Backbone.Collection.extend({
    model: app.BaseModel,
    sync: function(method, collection, options) {
        var url = options.url || _.result(model, 'url') || urlError();
        options.url = aop.API.applyParams(url);
        return app.BaseCollection.__super__.sync.apply(this, arguments);
    }
});

那么使用起来就这么简单:

app.MyModel = app.BaseModel.extend({
    urlRoot: app.API.url('item'),
})

app.Collection = app.BaseCollection.extend({
    model: app.MyModel,
    url: app.API.url('collection-items'),
});

下面的测试输出:

var app = app || {};
(function() {


  app.API = {
    protocol: 'https',
    domain: 'api.xxxxxx.com',
    root: '/v12_1/',
    params: {
      appId: 'xxxx',
      appKey: 'yyyy',
    },
    /**
     * Get the full API url, with your optional path.
     * @param  {String} path (optional) to add to the url.
     * @return {String}  full API url with protocol, domain, root.
     */
    url: function(path) {
      path = path || '';
      if (path.slice(-1) !== '/') path += '/';
      return this.protocol + "://" + this.domain + this.root + path;
    },
    /**
     * Adds the query string to the url, merged with the default API parameters.
     * @param  {String} url  (optional) before the query string
     * @param  {Object} params to transform into a query string
     * @return {String}   e.g.: "your-url?param=value&otherparam=123"
     */
    applyParams: function(url, params) {
      return (url || "") + "?" + $.param(_.extend({}, this.params, params));
    },
  };

  app.BaseModel = Backbone.Model.extend({
    setId: function(id, options) {
      return this.set(this.idAttribute, id, options);
    },
    url: function() {
      var base =
        _.result(this, 'urlRoot') ||
        _.result(this.collection, 'url') ||
        urlError();
      var id = this.get(this.idAttribute);
      return app.API.applyParams(base, this.isNew() || {
        id: encodeURIComponent(id)
      });
    },
  });

  app.BaseCollection = Backbone.Collection.extend({
    model: app.BaseModel,
    sync: function(method, collection, options) {
      var url = options.url || _.result(model, 'url') || urlError();
      options.url = aop.API.applyParams(url);
      return app.BaseCollection.__super__.sync.apply(this, arguments);
    }
  });

  app.MyModel = app.BaseModel.extend({
    urlRoot: app.API.url('item'),
  })

  app.Collection = app.BaseCollection.extend({
    model: app.MyModel,
    url: app.API.url('collection-items'),
  });

  var model = new app.MyModel();
  console.log("New model url:", model.url());
  model.setId("53444d0d7ba4ca15456f5690");
  console.log("Existing model url:", model.url());

  var collection = new app.Collection();
  console.log("collection url:", _.result(collection, 'url'));

  var modelUrlThroughCollection = new app.BaseModel({
    id: "test1234"
  });
  collection.add(modelUrlThroughCollection);
  console.log("model via collection:", modelUrlThroughCollection.url());
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>

New model url: https://api.xxxxxx.com/v12_1/item/?appId=xxxx&appKey=yyyy
Existing model url: https://api.xxxxxx.com/v12_1/item/?appId=xxxx&appKey=yyyy&id=53444d0d7ba4ca15456f5690
collection url: https://api.xxxxxx.com/v12_1/collection-items/
model via collection: https://api.xxxxxx.com/v12_1/collection-items/?appId=xxxx&appKey=yyyy&id=test1234

如何使用外部 API 填充模型?

Backbone.js gives structure to web applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing API over a RESTful JSON interface.

如果您使用的 API 遵守 REST 原则,则可能有一个 returns 对象数组的端点。这是集合应该获取其数据的地方。

app.Collection = app.BaseCollection.extend({
    model: app.MyModel,
    url: app.API.url('collection-items'),
});
var collection = new app.Collection();
// GET request to 
// https://api.xxxxxx.com/v12_1/collection-items/?appId=xxxx&appKey=yyyy
collection.fetch();

它应该收到类似的东西:

[
    { id: "24b6463n5", /* ... */ },
    { id: "345333bbv", /* ... */ },
    { id: "3g6g346g4", /* ... */ },
    /* ... */
]

如果您想将现有模型(使用 ID 引用)添加到集合中:

var model = new app.MyModel({
    // giving an id to a model will make call to fetch possible
    id: "53444d0d7ba4ca15456f5690" 
});

// GET request to 
// https://api.xxxxxx.com/v12_1/item/?appId=xxxx&appKey=yyyy&id=53444d0d7ba4ca15456f5690
model.fetch();
collection.add(model);

响应应该是单个对象:

{ id: "53444d0d7ba4ca15456f5690", /* ... */ }

如果要创建新模型:

var model = new app.MyModel({ test: "data", /* notice no id passed */ });
// POST request to
// https://api.xxxxxx.com/v12_1/item/?appId=xxxx&appKey=yyyy
model.save();
// or, equivalent using a collection:
collection.create({ test: "data", /* notice no id passed */ });

避免 .on/.bind 转而使用 .listenTo

在事件绑定上传递上下文对于 Backbone 很重要,因为大多数部分是 而 jQuery 回调通常是匿名的作用于局部变量的函数。除此之外,您应该使用 Backbone's listenTo 而不是 on

Backbone js .listenTo vs .on

listenTo is the newer and better option because these listeners will be automatically removed for you during stopListening which is called when a view gets removed (via remove()). Prior to listenTo there was a really insidious problem with phantom views hanging around forever (leaking memory and causing misbehavior)...


避免使用 jQuery

手动绑定事件

在视图中,您应该使用 events property 自动将 DOM 事件委托给视图的回调。它仍然在后台 jQuery,但更干净,已经集成到 Backbone 并且上下文自动传递,因此无需使用 var self = this 技巧。

app.MyModel2View = Backbone.View.extend({
    events: {
        "click .add-myModel": "onAddModelClick",
    },
    onAddModelClick: function() {
        this.model.myModels.add({});
    },
    // ...some code...
});

创建新模型并获取它在 Backbone 设计中没有任何意义,除非您将 id 传递给模型。只需使用空对象调用 add on the collection 即可创建默认模型。