Ember Model

model是指数据层,也就是一个用于向用户展示的数据对象。考虑到前后端分离,其实可以认为展现内容与后端的DTO对象或者DVO对象是一一对应的。同时,model会通过json的形式以http请求的方式与服务器进行交互。但是,model的形式不一定是json的,其他的形式都是可以的。也就是说,model是持久化的。

这种方式下,ember本身提供了许多api,这也就是Ember data做的事情。Ember Data为你提供了更加简便的方式操作数据,统一管理数据的加载,降低程序复杂度。

Ember Data有几个重要的概念,分别为:modelsrecordsadaptersstoreserializer.

models

个人认为,model这个类,应该和后端的DO或者是DTO对象是一一对应的。
model的定义,可以通过命令行方式:
ember g model <your-model-name>
则会创建一个app/models/<your-model-name>.js的文件。其中就是model的主体,由若干个属性构成,如:

export default DS.Model.extend({  
    title: DS.attr('string'),  //  字符串类型
    flag: DS.attr('boolean'), //  布尔类型
    timestamp: DS.attr('number'),  //  数字类型
    birth: DS.attr('date'),  //日期类型
    addr: DS.attr(),  //json类型,其实就是string
});

这里需要注意,没有id字段,Ember 会默认生成id属性。
如果有对应关系的话,比如一对多或者多对一的情况,那么可以使用Ember Data中的hasMany 以及 belongsTo方法。belongsTo为一对一关系,hasMany为一对多关系。使用方式如下

import DS from 'ember-data';

//app/models/blog-post.js
export default DS.Model.extend({
  comments: DS.hasMany('comment')
});

//app/models/commont.js
export default DS.Model.extend({
  blogPost: DS.belongsTo('blog-post')
});

如果是多对多的关系,则如下就好:

import DS from 'ember-data';

//app/models/blog-post.js
export default DS.Model.extend({
  comments: DS.hasMany('comment')
});

//app/models/commont.js
export default DS.Model.extend({
  blogPost: DS.hasMany('blog-post')
});

这个对应关系,可以看做为,实际的逻辑表之间的对应关系。

此外,还可以添加计算属性,因为,model也是一个Ember Object

export default DS.Model.extend({  
    title: DS.attr('string'),  //  字符串类型
    flag: DS.attr('boolean'), //  布尔类型
    tf: Ember.computed('title', 'flag', function() {
      return `${this.get('title')} ${this.get('flag')}`;
    })
});

store & record

store这个概念,在Ember Data中十分重要。
store是指所有的从服务端活动的record的数据以及新创建的record数据。即,store为所有数据的缓存,所有关于record的增删改查动作,都需要经过store。

一般情况下,一个应用,只有一个DS.store,它是由Ember.Application初始化的。

record是指一个model的实例,他可能是一个从服务端返回的数据,当然,你自己也可以新建一个record。

假设我们有一个route:

//  app/routes/store-route.js
import Ember from 'ember';
export default Ember.Route.extend({  
    model: function() {
    }
});

store 这个概念,可以在route以及controller中使用。形如this.get('store') 或者 this.store

查询record

(1)store.findRecord() 通过model的type以及id,发起一个请求,去获取数据,他返回一个promise对象,结果为record。
model(){
    return this.store.findRecord('model-example',1).then((data) => {
        return data;
    });
    //get请求为 域名/model-examples/1, 例如 http://localhost:4200/model-examples/1
}
(2)store.peekRecord(), 通过model的type以及id,不发起一个请求,从store中去获取现有以加载的数据。
model(){
    return this.store.peekRecord('model-example',1);
    //无请求
}
(3)store.findAll(), 通过model的type获取所有的数据,发起请求,返回的为,DS.PromiseArray对象,结果为DS.RecordArray
model(){
    return this.store.findAll('model-example').then((data)=>{return data});
    //get请求为 域名/model-examples, 例如 http://localhost:4200/model-examples
}
(4)store.peekAll(), 通过model的type获取所有的数据,不发起请求,从store中去获取现有以加载的数据,返回的为DS.RecordArray
model(){
    this.store.findAll('model-example');
    //无请求
}

这里需要注意,DS.RecordArray,并不是一个[]它使一个可遍历的对象。这意味着,如果你在模板中,使用了这些reocrd,那么就可以享受到双向绑定的好处,同时,也提供了一些API,能够帮助你更方便的使用。具体可参考:Ember.js DS.Store

(5)store.query()条件查询,查询条件为type以及自定义条件,同样也是一个get请求,返回结果通store.findAll()
model(){
    this.store.query('model-example',{firstName:'James'});
    //get 请求,请求为 域名/model-examples?firstName=James,例如,http://localhost:4200/model-examples?firstName=James
}
(6)store.queryRecord()条件查询,查询条件为type以及自定义条件,同样也是一个get请求,返回结果同store.findRecord()
model(){
    this.store.queryRecord('model-example',{firstName:'James'});
    //get 请求,请求为 域名/model-examples?firstName=James,例如,http://localhost:4200/model-examples?firstName=James,返回结果为promise,其中为一个record
}

创建record

使用store.createRecord()来增加一个record。

model(){
    let a =  this.store.createRecord('model-example',{
        firstName:'firstName',
        secondName: 'secondName'
    });
    return a;
}

则返回一个record。

在运用场景上,我们可以设想这样一个场景,比如一个表单:

//route route-example.hbs
<form class="" action="index.html" method="post">
    firstName:{{input value=model.firstName}}
    secondName:{{input value=model.secondName}}
    <button type="submit" name="button" {{action 'submitForm'}}>提交</button>
</form>
//route route-example.js
import Ember from 'ember';
export default Ember.Route.extend({
    model(){
        return  this.store.createRecord('model-example',{
            firstName:'',
            secondName: ''
        });
    },
    actions:{
        actionSubmit(){
            let model = this.modelFor('route-example');
            model.save();
            //这里会发出一个post请求,即 域名/model-example, 这里是localhost:8000/model-example,参数为{"data":{"attributes":{"first-name":"<你填写的内容>","second-name":"<你填写的内容>"},"type":"model-examples"}}
        }
    }
});

生成的页面如下:
这里写图片描述

这的数据是一个双向绑定的过程,在页面上的变动会直接变化到model上。

更新record

更新record的操作也是一样的

this.store.findRecord('model-example', 1).then(function(data) {
  data.set('firstName', "name1");
});

这样就可以对其进行修改。

持久化record

持久化,也就是说将数据存入数据库,实际上是一个与后端进行交互的过程。
调用sava()方法就可以。
但是不同于新建的post请求,所有更新操作的请求为PATCH请求。

this.store.findRecord('model-example', 1).then(function(data) {
  data.set('firstName', "name1");
  data.save()
  //patch 请求:域名/model-examples/1, 在这里为:http://localhost:4200/model-examples/1
  //参数 {"data":{"id":"1","attributes":{"firstName":"asdas","secondName":"sn"},"type":"model-examples"}}
});

删除record

使用deleteRecord()可以删除一个record

this.store.findRecord('model-example', 1).then(function(data) {
  data.deleteRecord();
  data.get('isDeleted');//true =>表示已经删除
});

如果想要持久化到后端,调用sava()

this.store.findRecord('model-example', 1).then(function(data) {
  data.deleteRecord();
  data.get('isDeleted');//true =>表示已经删除
  data.save();//这会发送一个DELETE请求,为 http://localhost:4200/model-examples/1
});

或者直接调用

this.store.findRecord('model-example', 1).then(function(data) {
  data.destroyRecord();//这会发送一个DELETE请求,为 http://localhost:4200/model-examples/1
});
插入record

ember还支持直接向store中插入record,但是这个过程不是与后端交互的过程。是通过push()方法实现的。

//route route-example.js
export default Ember.Route.extend({
  model() {
    this.get('store').push({
      data: [{
        id: 1,
        type: 'model-example',
        attributes: {
          firstName: 'fn1',
          secondName: 'sn1',
        },
      }, {
          id: 1,
          type: 'model-example',
          attributes: {
          firstName: 'fn1',
          secondName: 'sn1',
        },
      }]
    });
  }
});

这样就可以手动向其中插入两条record。例如,当时不想用他提供的方式如post、patch、delete进行增删改数据,那么可以使用ajax先更新或者插入数据,然后在手动push进store。

小结

总结一下增删改查的请求形式:
//TODO

adapters

适配器,决定了数据如何持久化到后端。举例而言,请求的URL、REST API的header等。
创建适配器的命令,同样通过命令行就可以创建:ember g adapter <your-adapter-name>。这里注意,这里的名字必须和路由的是一致的,关于路由的规则,我们后面独立说。
application这个路由为例,其适配器配置可能为:

//app/adapters/application.js
import DS from 'ember-data';
export default DS.JSONAPIAdapter.extend({
  host: 'http://xxx.com',
  namespace: 'api/v1',
  pathForType: function(type) {
    return Ember.String.underscore(type);
  },
  headers: {
    'API_KEY': 'secret key',
    'ANOTHER_HEADER': 'Some header value'
  }
});

下面解释一下这几个常用规则的配置:

  • host,指域名
  • namespace,命名空间,如上述的情况下,model名为model-example的新建请求为(忽略pathForType方法):http://xxx.com/api/v1/model-example
  • pathForType,修改model的适配方式,如果你希望都是下划线的方式,可以调用Ember.String.underscore()方法,就可以把驼峰或者中划线改为下划线,那么上述的情况下,新建的请求为:http://xxx.com/api/v1/model_example
  • headers,http的heads,这里是支持计算属性的,例如:
export default DS.JSONAPIAdapter.extend({
  session: Ember.inject.service('session'),
  headers: Ember.computed('session.authToken', function() {
    return {
      'API_KEY': this.get('session.authToken'),//这里的headers取自session的authToken字段
      'ANOTHER_HEADER': 'Some header value'
    };
  })
});

在适配器中,你还可以重写函数findRecord() findAll……等等:

export default DS.JSONAPIAdapter.extend({
    findRecord(store, type, id){
        return Ember.$.getJson(`${this.get('host')}/${this.get('namespace')}/${type}/${id}`);
    }
});

序列化器serializers

序列化器serializers主要负责用来格式化数据。这个在Ember Data中十分重要,因为本身model的数据格式十分蛋疼,用起来非常难受。

创建serializers,也可以是命令行工具:ember g serializer <your-serializer-name>

Ember Data提供了3种serializers的默认实现,分别为:

  • JSONAPISerializer,2.0以后版本的默认实现
  • JSONSerializer 针对单个json或者json array的简单实现
  • RESTSerializer2.0以前版本的默认实现,较为复杂,可以支持边缘加载,即是通过增加数据层级来分步加载(可以理解为先加载第一层次的数据集合,然后按需加载子一级的数据集合)

这意味着,根据自己的需求,继承其中任意一个类就可以。

现在考虑标准格式的record:

//单条record
{
  "data": {
    "type": "people",
    "id": "123",
    "attributes": {
      "first-name": "Jeff",
      "last-name": "Atwood"
    }
  }
}
//recordArray
{
  "data": [{
    "type": "model-example",
    "id": "1",
    "attributes": {
      "firstName": "Jeff",
      "secondName": "Atwood"
    }
  }, {
    "type": "model-example",
    "id": "2",
    "attributes": {
      "firstName": "Yehuda",
      "secondName": "Katz"
    }
  }]
}
//如果数据除了本身之外还有关系数据,那么关系数据就放在`included`中
{
  "data": {
    "type": "articles",
    "id": "1",
    "attributes": {
      "title": "JSON API paints my bikeshed!"
    },
    "links": {
      "self": "http://example.com/articles/1"
    },
    "relationships": {
      "comments": {
        "data": [
          { "type": "comments", "id": "5" },
          { "type": "comments", "id": "12" }
        ]
      }
    }
  },
  "included": [{
    "type": "comments",
    "id": "5",
    "attributes": {
      "body": "First!"
    },
    "links": {
      "self": "http://example.com/comments/5"
    }
  }, {
    "type": "comments",
    "id": "12",
    "attributes": {
      "body": "I like XML better"
    },
    "links": {
      "self": "http://example.com/comments/12"
    }
  }]
}

我们从简单的情况入手,比如,正常情况下,我们的后端返回的数据格式可能为:

{
    "result":[
        {
          "id": 1,
          "firstName": "Jeff",
          "secondName": "Atwood"
        },
        {
          "id": 1,
          "firstName": "Yehuda",
          "secondName": "Katz"
        }   
    ]
}

这种情况下,如果返回的话,前端一定会报错,因为格式不匹配,找不到data

格式化内容数据
import DS from 'ember-data';

export default DS.JSONAPISerializer.extend({
  normalizeResponse(store, primaryModelClass, payload, id, requestType) {
    if (payload) {
        if (!payload.result){
            let documentHash = {included:[]};
            if(id)
                documentHash.data=null;
            else
                documentHash.data=[];
            return documentHash;
        }
        return this._super(store, primaryModelClass, payload.result, id, requestType);
    }
    return this._super(...arguments);
  },
});

这样,就可以将返回的result转化为data

反之,如果请求中,后端需要的内容与前端给的不一样,只需要重写serialize方法:


import DS from 'ember-data';

export default DS.JSONSerializer.extend({  
    serialize: function(snapshot, options) {
        var json = this._super(...arguments);  // ??
        json.data.attributes.cost = {
            amount: json.data.attributes.amount,
            currency: json.data.attributes.currency
        };

        delete json.data.attributes.amount;
        delete json.data.attributes.currency;

        return json;
    }
});

通过上述的方式,可以把前端发送的内容:

{
  "data": {
    "attributes": {
      "id": "1",
      "name": "My Product",
      "amount": 100,
      "currency": "SEK"
    },
    "type": "product"
  }
}

变为:

{
  "data": {
    "attributes": {
      "id": "1",
      "name": "My Product",
      "cost": {
        "amount": 100,
        "currency": "SEK"
      }
    },
    "type": "product"
  }
}

最后说两句,RESTSerializer的实现与其他两个不同,他没有规定返回的格式,也就是说可以是任意格式的,而其他的都是json。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值