model是指數據層,也就是一個用于向用戶展示的數據對象??紤]到前后端分離,其實可以認為展現內容與后端的DTO對象或者DVO對象是一一對應的。同時,model會通過json的形式以http請求的方式與服務器進行交互。但是,model的形式不一定是json的,其他的形式都是可以的。也就是說,model是持久化的。
這種方式下,ember本身提供了許多api,這也就是Ember data做的事情。Ember Data為你提供了更加簡便的方式操作數據,統一管理數據的加載,降低程序復雜度。
Ember Data有幾個重要的概念,分別為:models
、records
、adapters
、store
、serializer
.
個人認為,model這個類,應該和后端的DO或者是DTO對象是一一對應的。 model的定義,可以通過命令行方式: ember g model <your-model-name>
則會創建一個app/models/<your-model-name>.js
的文件。其中就是model的主體,由若干個屬性構成,如:
這里需要注意,沒有id字段,Ember 會默認生成id屬性。 如果有對應關系的話,比如一對多或者多對一的情況,那么可以使用Ember Data中的hasMany
以及 belongsTo
方法。belongsTo
為一對一關系,hasMany
為一對多關系。使用方式如下
如果是多對多的關系,則如下就好:
import DS from 'ember-data';//app/models/blog-post.jsexport default DS.Model.extend({ comments: DS.hasMany('comment')});//app/models/commont.jsexport 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這個概念,在Ember Data中十分重要。 store是指所有的從服務端活動的record的數據以及新創建的record數據。即,store為所有數據的緩存,所有關于record的增刪改查動作,都需要經過store。
一般情況下,一個應用,只有一個DS.store,它是由Ember.application初始化的。
record是指一個model的實例,他可能是一個從服務端返回的數據,當然,你自己也可以新建一個record。
假設我們有一個route:
// app/routes/store-route.jsimport Ember from 'ember';export default Ember.Route.extend({ model: function() { }});store 這個概念,可以在route以及controller中使用。形如this.get('store')
或者 this.store
。
store.findRecord()
通過model的type以及id,發起一個請求,去獲取數據,他返回一個PRomise對象,結果為record。store.peekRecord()
, 通過model的type以及id,不發起一個請求,從store中去獲取現有以加載的數據。store.findAll()
, 通過model的type獲取所有的數據,發起請求,返回的為,DS.PromiseArray對象,結果為DS.RecordArraystore.peekAll()
, 通過model的type獲取所有的數據,不發起請求,從store中去獲取現有以加載的數據,返回的為DS.RecordArray這里需要注意,DS.RecordArray,并不是一個[]
它使一個可遍歷的對象。這意味著,如果你在模板中,使用了這些reocrd,那么就可以享受到雙向綁定的好處,同時,也提供了一些API,能夠幫助你更方便的使用。具體可參考:Ember.js DS.Store
store.query()
條件查詢,查詢條件為type以及自定義條件,同樣也是一個get請求,返回結果通store.findAll()
store.queryRecord()
條件查詢,查詢條件為type以及自定義條件,同樣也是一個get請求,返回結果同store.findRecord()
使用store.createRecord()
來增加一個record。
則返回一個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.jsimport 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的操作也是一樣的
this.store.findRecord('model-example', 1).then(function(data) { data.set('firstName', "name1");});這樣就可以對其進行修改。
持久化,也就是說將數據存入數據庫,實際上是一個與后端進行交互的過程。 調用sava()
方法就可以。 但是不同于新建的post請求,所有更新操作的請求為PATCH
請求。
使用deleteRecord()
可以刪除一個record
如果想要持久化到后端,調用sava()
或者直接調用
this.store.findRecord('model-example', 1).then(function(data) { data.destroyRecord();//這會發送一個DELETE請求,為 http://localhost:4200/model-examples/1});ember還支持直接向store中插入record,但是這個過程不是與后端交互的過程。是通過push()
方法實現的。
這樣就可以手動向其中插入兩條record。例如,當時不想用他提供的方式如post、patch、delete進行增刪改數據,那么可以使用Ajax先更新或者插入數據,然后在手動push進store。
總結一下增刪改查的請求形式: //TODO
適配器,決定了數據如何持久化到后端。舉例而言,請求的URL、REST API的header等。 創建適配器的命令,同樣通過命令行就可以創建:ember g adapter <your-adapter-name>
。這里注意,這里的名字必須和路由的是一致的,關于路由的規則,我們后面獨立說。 以application
這個路由為例,其適配器配置可能為:
下面解釋一下這幾個常用規則的配置:
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
……等等:
序列化器serializers主要負責用來格式化數據。這個在Ember Data中十分重要,因為本身model的數據格式十分蛋疼,用起來非常難受。
創建serializers,也可以是命令行工具:ember g serializer <your-serializer-name>
Ember Data提供了3種serializers的默認實現,分別為:
JSONAPISerializer
,2.0以后版本的默認實現JSONSerializer
針對單個json或者json array的簡單實現RESTSerializer
2.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" }, "relationshxml better" }, "links": { "self": "http://example.com/comments/12" } }]}我們從簡單的情況入手,比如,正常情況下,我們的后端返回的數據格式可能為:
{ "result":[ { "id": 1, "firstName": "Jeff", "secondName": "Atwood" }, { "id": 1, "firstName": "Yehuda", "secondName": "Katz" } ]}這種情況下,如果返回的話,前端一定會報錯,因為格式不匹配,找不到data
。
這樣,就可以將返回的result
轉化為data
。
反之,如果請求中,后端需要的內容與前端給的不一樣,只需要重寫serialize
方法:
通過上述的方式,可以把前端發送的內容:
{ "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。
新聞熱點
疑難解答