寫在前面
最近比較忙,換了新工作還要學習很多全新的技術棧,並給自己找了很多借口來不去堅持寫博客。常常具有諷刺意味的是,更多剩下的時間並沒有利用而更多的是白白浪費,也許這就是青春吧,揮霍吧,這不是我想要的,既然這樣,我還要繼續寫下去,堅持把博客做好,爭取進前100博客,在此謹記。
2015年5月7日深夜,於電腦旁。
文章索引
定義模型
Ember-data中所有的模型都繼承自DS.Model,模型內部的屬性是通過DS.attr來聲明的。
var attr = DS.attr; App.Person = DS.Model.extend({ firstName: attr(), lastName: attr(), birthday: attr() });
此外,DS.Model中還可以添加“計算屬性”:
App.Person = Ember.Object.extend({ // these will be supplied by `create` firstName: null, lastName: null, fullName: function() { return this.get('firstName') + ' ' + this.get('lastName'); }.property('firstName', 'lastName') }); var ironMan = App.Person.create({ firstName: "Tony", lastName: "Stark" }); ironMan.get('fullName') // "Tony Stark"
特別的,還可以顯式指定屬性返回值類型:
App.Person = DS.Model.extend({ birthday: DS.attr('date') });
默認情況下,REST 適配器支持的屬性類型有string
, number
, boolean
和date
。 傳統的適配器會提供額外的屬性類型,並支持你注冊自定義的屬性類型。 詳情請查看documentation section on the REST Adapter。
請注意:date類型參考ISO8601,例如:2014-05-27T12:54:01
選項
目前,Ember-data只支持一種默認的選項:
export default DS.Model.extend({ username: DS.attr('string'), email: DS.attr('string'), verified: DS.attr('boolean', {defaultValue: false}), createdAt: DS.attr('string', { defaultValue: function() { return new Date(); } }) });
定義關聯模型
Ember Data 包括了幾個內置的關聯類型,以幫助你確定你的模型如何相互關聯的。
一對一
使用DS.belongsTo
在兩個模型間聲明一對一的關系。
App.User = DS.Model.extend({ profile: DS.belongsTo('profile') }); App.Profile = DS.Model.extend({ user: DS.belongsTo('user') });
一對多
使用DS.belongsTo
結合DS.hasMany
來聲明兩個模型間的一對多關系,示例如下:
App.Post = DS.Model.extend({ comments: DS.hasMany('comment') }); App.Comment = DS.Model.extend({ post: DS.belongsTo('post') });
多對多
使用DS.hasMany
來聲明兩個模型間的多對多關系。
App.Post = DS.Model.extend({ tags: DS.hasMany('tag') }); App.Tag = DS.Model.extend({ posts: DS.hasMany('post') });
顯式反轉
Ember Data會盡最大努力去自動發現關聯關系的映射關系。在上例的一對多的情況下,修改了comments
會自動更新post
,應為這是唯一的一個關聯模型。
但是,有時候對同一個類型有多個belongsTo
/hasMany
關聯關系。這時可以通過指定在反向端使用DS.hasMany
的inverse
選項來指定其關聯的模型:
var belongsTo = DS.belongsTo, hasMany = DS.hasMany; App.Comment = DS.Model.extend({ onePost: belongsTo("post"), twoPost: belongsTo("post"), redPost: belongsTo("post"), bluePost: belongsTo("post") }); App.Post = DS.Model.extend({ comments: hasMany('comment', { inverse: 'redPost' }) });
當然也可以在belongsTo
一側指定,它將按照預期那樣工作。
創建和刪除記錄
創建
通過調用倉庫的createRecord
方法,可以創建記錄:
store.createRecord('post', { title: 'Rails is Omakase', body: 'Lorem ipsum' });
盡管createRecord
的使用已經非常直接,但是還需要注意一點,就是目前還不支持將一個承諾賦值給一個關聯。
例如,如果希望給文章設置author
屬性,如果指定ID的user
並沒有加載到倉庫中的話,下面的代碼將不會正常工作。
var store = this.store; store.createRecord('post', { title: 'Rails is Omakase', body: 'Lorem ipsum', author: store.find('user', 1) });
不過在承諾履行時可以非常方便的進行關聯關系的設置:
var store = this.store; var post = store.createRecord('post', { title: 'Rails is Omakase', body: 'Lorem ipsum' }); store.find('user', 1).then(function(user) { post.set('author', user); });
刪除
刪除記錄與創建記錄一樣簡單。只需要調用DS.Model
實例的deleteRecord()
方法即可。這將會吧記錄標記為isDeleted
,並且不在store
的all()
查詢中返回。刪除操作之后會通過使用save()
來進行持久化。此外,也可以使用destroyRecord
來將刪除和持久化一次完成。
var post = store.find('post', 1); post.deleteRecord(); post.get('isDeleted'); // => true post.save(); // => DELETE to /posts/1 // OR var post = store.find('post', 2); post.destroyRecord(); // => DELETE to /posts/2
將記錄推送進倉庫
上節講到倉庫的概念,倉庫類似於數據的緩沖池,當應用程序向倉庫中查找不存在數據時,倉庫則向數據源發起數據請求,如果存在的話,直接返回倉庫中的數據。
推送記錄
為了將記錄推入倉庫,需要調用倉庫的push()
方法。
var attr = DS.attr; App.Album = DS.Model.extend({ title: attr(), artist: attr(), songCount: attr() }); App.ApplicationRoute = Ember.Route.extend({ model: function() { this.store.push('album', { id: 1, title: "Fewer Moving Parts", artist: "David Bazan", songCount: 10 }); this.store.push('album', { id: 2, title: "Calgary b/w I Can't Make You Love Me/Nick Of Time", artist: "Bon Iver", songCount: 2 }); } });
持久化記錄
Ember Data中的記錄都基於實例來進行持久化。調用DS.Model
實例的save()
會觸發一個網絡請求,來進行記錄的持久化。
var post = store.createRecord('post', { title: 'Rails is Omakase', body: 'Lorem ipsum' }); post.save(); // => POST to '/posts'
承諾
save()
會返回一個承諾,這使得可以非常容易的來處理保存成功和失敗的場景。下面是一個通用的模式:
var post = store.createRecord('post', { title: 'Rails is Omakase', body: 'Lorem ipsum' }); var self = this; function transitionToPost(post) { self.transitionToRoute('posts.show', post); } function failure(reason) { // handle the error } post.save().then(transitionToPost).catch(failure); // => POST to '/posts' // => transitioning to posts.show route
對於失敗的網絡請求,承諾也可以方便的來處理:
var post = store.createRecord('post', { title: 'Rails is Omakase', body: 'Lorem ipsum' }); var onSuccess = function(post) { this.transitionToRoute('posts.show', post); }; var onFail = function(post) { // deal with the failure here }; post.save().then(onSuccess, onFail); // => POST to '/posts' // => transitioning to posts.show route
更多關於承諾的內容請參看這里,下面是一個示例展示了如何在重試失敗的持久化操作:
function retry(callback, nTimes) { // if the promise fails return callback().fail(function(reason) { // if we haven't hit the retry limit if (nTimes-- > 0) { // retry again with the result of calling the retry callback // and the new retry limit return retry(callback, nTimes); } // otherwise, if we hit the retry limit, rethrow the error throw reason; }); } // try to save the post up to 5 times retry(function() { return post.save(); }, 5);
查詢記錄
Ember Data倉庫提供了一個非常簡單的查詢一類記錄的接口,該接口就是store
對象的find
方法。在內部,store
根據傳入的參數使用find
、findAll
和findQuery
完成查詢。store.find()
的第一個參數是記錄的類型,第二個可選參數確定查詢是獲取所有記錄,還是一條記錄,還是特定的記錄。
查詢一個類型的所有記錄
var posts = this.store.find('post');
如果希望獲取已經加載到倉庫中的記錄的列表,而不希望通過一個網絡請求去獲取,可以使用all
方法:
var posts = this.store.all('post'); // => no network request
find
會返回一個將使用DS.RecordArray
來履行的DS.PromiseArray
,而all
直接返回DS.RecordArray
。
需要重點注意的一點是DS.RecordArray
不是一個Javascript數組。它是一個實現了Ember.Enumerable
的對象。這一點非常重要,因為例如希望通過索引獲取記錄,那么[]
將無法工作,需要使用objectAt(index)
來獲取。
查詢一個記錄
如果調用store.find()
方法時,第二個參數是一個數字或者字符串,Ember Data將嘗試獲取對應ID的記錄。find()
方法將返回一個用請求的記錄來履行的承諾。
var aSinglePost = this.store.find('post', 1); // => GET /posts/1
查詢記錄
如果傳遞給find
方法的第二個參數是一個對象,Ember Data會發送一個使用該對象來序列化出來的查詢參數的GET
請求。這是方法返回與不加第二個參數時候一樣的DS.PromiseArray
。
例如,可以查詢名為Peter
的person
模型的所有記錄:
var peters = this.store.find('person', { name: "Peter" }); // => GET to /persons?name='Peter'
與路由的模型鈎子集成
如同在指定路由的模型一節中討論的一樣,路由是負責告訴模板將渲染哪個模型。
Ember.Route
的model
鈎子支持立即可用的異步值。如果model
鈎子返回一個承諾,路由將等待承諾履行條件滿足時才渲染模板。
這使得使用Ember Data的異步數據來編寫應用變得容易。只需要通過model
鈎子返回請求的記錄,交個Ember來處理是否需要一個網絡請求。
App.Router.map(function() { this.resource('posts'); this.resource('post', { path: ':post_id' }); }); App.PostsRoute = Ember.Route.extend({ model: function() { return this.store.find('post'); } }); App.PostRoute = Ember.Route.extend({ model: function(params) { return this.store.find('post', params.post_id); } });
使用記錄
修改屬性
一旦一條記錄已經加載進來,你就可以開始修改它的屬性(attributes)了。屬性(attributes)和Ember.js中對象的普通屬性(properties)差不多。
var tyrion = this.store.find('person', 1); // ...after the record has loaded tyrion.set('firstName', "Yollo");
你可以通過isDirty屬性來判斷一條記錄是否被更改,且尚未保存。此外使用changedAttributes
函數還可以查看記錄哪些部分被修改了,以及這些部分被修改前的值是什么。changedAttributes
返回一個對象,其鍵值是被改變的屬性,而值是一個數組[oldValue, newValue]
。
person.get('isAdmin'); //=> false person.get('isDirty'); //=> false person.set('isAdmin', true); person.get('isDirty'); //=> true person.changedAttributes(); //=> { isAdmin: [false, true] }
此時,可以通過save()
將改變持久化,也可以回滾做的改變。調用rollback()
會將所有changedAttributes
設置回其原來的值。
person.get('isDirty'); //=> true person.changedAttributes(); //=> { isAdmin: [false, true] } person.rollback(); person.get('isDirty'); //=> false person.get('isAdmin'); //=> false person.changedAttributes(); //=> {}
使用Fixture
當開發客戶端應用時,服務端可能還沒有完成對應API的開發。使用FixtureAdapter
可以先進行Ember應用開發,然后切換到其他的適配器來使用API,並且這個切換並不需要改變應用的代碼。
使用夾具適配器需要完成三步簡單的配置:
- 創建一個新的、使用夾具適配器的store並將其關聯到應用。
- 使用DS.Model.extend來定義模型。
- 將夾具(樣本數據)關聯到對應的模型類。
創建夾具適配器
只需將其定義為Ember.Application
應用的ApplicationAdapter
屬性即可。
var App = Ember.Application.create(); App.ApplicationAdapter = DS.FixtureAdapter;
定義模型
這里定義一個編寫Ember文檔的人員的模型:
App.Documenter = DS.Model.extend({ firstName: DS.attr( 'string' ), lastName: DS.attr( 'string' ) });
關聯夾具與模型類
關聯夾具非常之簡單。只需要將一個Javascript對象集合賦值給模型的FIXTURES
屬性即可:
App.Documenter.FIXTURES = [ { id: 1, firstName: 'Trek', lastName: 'Glowacki' }, { id: 2, firstName: 'Tom' , lastName: 'Dale' } ]
在Route中使用:
App.DocumenterRoute = Ember.Route.extend({ model: function() { return this.store.find('documenter', 1); // returns a promise that will resolve // with the record representing Trek Glowacki } });
命名慣例
與REST Adapter不同,夾具適配器並不做任何命名慣例的猜測。如同上例中所示,如果在DS.Model.extend
中定義了一個firstName
屬性,那么需要在夾具樣本數據中使用firstName
來表示該屬性。
更重要地是,需要確保夾具樣本數據中得每一條記錄都有一個唯一的標識。缺省情況下,Ember Data假定該標識為id
。如果沒有重定義主鍵標識名,又未在記錄中提供一個id
,那么夾具適配器會拋出一個錯誤。
連接Http服務器
如果Ember應用需要從HTTP服務器加載JSON數據,本指南將介紹如何配置Ember Data來從服務器端加載記錄,不論服務器返回的數據格式是什么樣子。
倉庫使用了一個稱為適配器,知道如何通過網絡進行通信的對象。默認情況下,倉庫會使用DS.RESTAdapter,一個可以與HTTP服務器通過XHR進行JSON數據交互的適配器。
本指南分為兩部分。第一部分介紹適配器的默認行為,包括將從哪些URL獲取記錄,和期望的JSON格式。
第二部分介紹如何重新定義這些默認行為,如請求數據的URL和JSON的結構。
URL約定
REST適配器使用模型的名稱來判定將要發送JSON的URL。
例如,用ID來請求一個App.Photo的記錄:
App.PhotoRoute = Ember.Route.extend({ model: function(params) { return this.store.find('photo', params.photo_id); } });
REST適配器將發送一個GET請求到/photos/1。
可以在記錄上執行的操作,在REST適配器中被映射到如下的URL上:
操作 |
HTTP Verb |
URL |
查詢 |
GET |
/people/123 |
查詢所有 |
GET |
/people |
更新 |
PUT |
/people/123 |
創建 |
POST |
/people |
刪除 |
DELETE |
/people/123 |
Json約定
給定模型如下:
var attr = DS.attr, hasMany = DS.hasMany, belongsTo = DS.belongsTo; App.Post = DS.Model.extend({ title: attr(), comments: hasMany('comment'), user: belongsTo('user') }); App.Comment = DS.Model.extend({ body: attr() });
Ember Data期望一個發送到/posts/1
請求將返回如下的JSON格式:
{ "post": { "id": 1, "title": "Rails is omakase", "comments": ["1", "2"], "user" : "dhh" }, "comments": [{ "id": "1", "body": "Rails is unagi" }, { "id": "2", "body": "Omakase O_o" }] }
自定義適配器
自定義REST適配器,需要定義一個DS.RESTAdapter
的子類,並將其命名為App.ApplicationAdapter
。接下來只需要重新定義它的屬性和方法,就可以自定義記錄如何加載和保存了。
自定義URL
URL前綴
如果JSON API並不在主機的根目錄,而是在一個其他的路徑下,那么需要為請求設置一個URL前綴。
例如,在使用了版本化的JSON API時,請求一個特定的person可能需要發送請求到/api/v1/people/1。
這種情況下,需要設置namespace屬性為api/v1。
App.ApplicationAdapter = DS.RESTAdapter.extend({ namespace: 'api/v1' });
現在請求一個ID為1
的person
就會發送請求到/api/v1/people/1
。
URL主機
如果JSON API的主機與提供Ember應用服務的主機不同,那么需要修改發送HTTP請求的主機。
注意為了使其正常工作,需要使用支持CORS的瀏覽器,並且服務器需要配置支持發送正確的CORS頭。
修改請求對應的目標主機,需要設置host
屬性:
App.ApplicationAdapter = DS.RESTAdapter.extend({ host: 'https://api.example.com' });
現在請求一個ID為1
的person
就會發送請求到https://api.example.com/people/1
。
自定義HTTP頭
一些API需要指定HTTP頭,例如提供一個API密鑰。任意鍵值對HTTP頭可以通過RESTAdapter的headers屬性來進行設置,Ember Data會將這些頭設置到到每個Ajax請求中去。
例如:
App.ApplicationAdapter = DS.RESTAdapter.extend({ headers: { "API_KEY": "secret key", "ANOTHER_HEADER": "Some header value" } });
請求任意資源都會包含如下的HTTP頭。
ANOTHER_HEADER: Some header value
API_KEY: secret key
處理元數據
與從倉庫中返回的記錄的同時,有時候需要處理一些元數據。元數據是除記錄外,與特定模型或類型一同的數據。
分頁是常見的一種元數據。例如一個博客擁有一次無法顯示完的文章,那么就需要使用如下的查詢:
this.store.findQuery("post", { limit: 10, offset: 0 });
為了獲取不同頁面的數據,只需要以10為增量來修改offset。到目前為止,一切都正常。現在有一個問題,就是如何知道擁有多少頁數據呢?服務器需要以元數據的形式返回所有的記錄數。
默認情況下,Ember Data的JSON反序列化會查找一個名為meta的鍵:
{ "post": { "id": 1, "title": "Progressive Enhancement is Dead", "comments": ["1", "2"], "links": { "user": "/people/tomdale" }, // ... }, "meta": { "total": 100 } }
特定類型的元數據將被設置到meta
中。可以使用store.metadataFor
來獲取。
var meta = this.store.metadataFor("post");
現在meta.total可以用來計算擁有多少頁文章了。
此外,通過重寫extractMeta方法,可以自定義元數據抽取的方法。例如,服務器沒有使用meta對象來返回元數據,而是返回了下面的格式:
{ "post": [ // ... ], "total": 100 }
那么可以這樣來抽取元數據:
App.ApplicationSerializer = DS.RESTSerializer.extend({ extractMeta: function(store, type, payload) { if (payload && payload.total) { store.metaForType(type, { total: payload.total }); // sets the metadata for "post" delete payload.total; // keeps ember data from trying to parse "total" as a record } } });
自定義適配器
在Ember Data中,處理與后台數據倉庫通信的邏輯是通過Adapter
來完成的。Ember Data適配器內置了一些關於REST API的假定。如果后台的實現與Ember Data假定的慣例不同,那么通過擴展缺省的適配器可能很容易的實現。
有時因為一些原因需要自定義適配器,例如需要使用下划線風格的URL,使用REST外的其他媒介來與后台通信,或者是使用本地后台.
擴展適配器在Ember Data中是一個常見的過程。Ember的立場是應該通過擴展適配器來添加不同的功能,而非添加標識。這樣可以使得代碼更加容易測試,更加易於理解,同時也降低了可能需要擴展適配器的用戶的代碼。
如果后台具有一定的一致性規則,那么可以定義一個ApplicationAdapter
。ApplicationAdapter
的優先級比缺省的適配器高,但是又會被模型特定的適配器取代。
App.ApplicationAdapter = DS.RESTAdapter.extend({ // Application specific overrides go here });
如果一個模型的后台有一些特殊的規則,那么可以定義一個模型特定的適配器,並將適配器命名為:"ModelName" + "Adapter"。
App.PostAdapter = DS.RESTAdapter.extend({ namespace: 'api/v1' });
缺省情況下,Ember Data內置了一些非常有用的適配器。可以根據自己的實際情況,選擇其中之一作為起點來自定義適配器。
DS.Adapter是最基礎的適配器,其自身不包含任何功能。如果需要創建一個與Ember適配器有根本性區別的適配器,那么可以這里入手。
DS.FixtureAdapter是一個用來從內存中加載記錄的適配器,常用於開發和測試階段。
DS.RESTAdapter是最通用的擴展適配器。RESTAdapter
可以實現store
與HTTP服務器之間通過XHR交互JSON數據。大部分使用JSON API的Ember應用都應該使用RESTAdapter
。
DS.ActiveModelAdapter是一個RESTAdapter
的特列,用於與Rails風格的REST API協同工作。
自定義RESTAdapter
DS.RESTAdapter是Ember Data提供的一個最通用的擴展適配器。它提供了一些非常有用的,可以擴展來與非標准化的后台接口通信的鈎子。
自定義端點路徑
namespace
屬性用來指定一個特定的url前綴。
App.ApplicationAdapter = DS.RESTAdapter.extend({ namespace: 'api/1' });
App.Person
的請求將會發至/api/1/people/1
。
自定義主機路徑
缺省情況下,適配器認為主機是當前域。如果希望指定一個新的域,那么可以通過設置適配器的host
屬性來指定。
App.ApplicationAdapter = DS.RESTAdapter.extend({ host: 'https://api.example.com' });
App.Person
的請求將會發至https://api.example.com/people/1
。
自定義路徑
缺省情況下,RESTAdapter嘗試將模型名進行駝峰化和復數化來作為路徑名。如果這種慣例並不符合使用的后台接口,可以通過重載pathForType方法來實現。
例如,並不需要將模型名稱復數化,需要采用下划線分割的模式替代駝峰命名,那么可以這樣來重載pathForType方法:
App.ApplicationAdapter = DS.RESTAdapter.extend({ pathForType: function(type) { return Ember.String.underscore(type); } });
App.Person
的請求將會發至/person/1
。 App.UserProfile
的請求將會發至/user_profile/1
。
制作適配器
defaultSerializer屬性可以用來指定適配器使用的序列化對象。這只在沒有模型特定的序列化對象,也沒有ApplicationSerializer的情況下。
在一個應用中,指定一個ApplicationSerializer比較容易。但是如果自定了一個通信的適配器,並且沒有指定一個ApplicationSerializer,那么設定defaultSerializer屬性,來確保Ember的行為正確性比較重要。
MyCustomAdapterAdapter = DS.RESTAdapter.extend({ defaultSerializer: '-default' });
社區適配器
如果Ember Data內置的適配器並不能很好的與使用的后台工作,可以查看社區維護的Ember Data適配器,看有不有合適的選擇。可以去一下地方去查找:
常見問題
應該使用查詢還是過濾條件來查詢記錄
這取決於想要查詢多少記錄和記錄是否已經加載到倉庫。
查詢比較適合於搜索成百上千甚至百萬級的記錄。需要做的只是將搜索條件發送給服務器端,然后搜索匹配的記錄的責任就交由服務器來處理。因為服務器返回的數據包含匹配的記錄的ID,所以倉庫即使之前沒有加載這些記錄也不會有影響;倉庫發現這些記錄並沒有被緩存會通過ID來發送請求獲取記錄。
使用查詢的缺點是它們無法得到自動更新,效率較低,並且要求服務器端支持希望執行的查詢類型。
因為服務器端決定與查詢匹配的記錄,而不是倉庫,查詢無法自動更新。如果需要更新查詢結果,需要手動的調用reload(),並等待服務器端返回。如果在客戶端創建了一個新的記錄,該記錄不會自動在結果中顯示,除非服務端已經包含了該記錄,並且重新加載了查詢結果。
因為倉庫必須讓服務器端來決定查詢的結果,因此需要一個網絡請求。這會讓用戶覺得緩慢,特別是在用於與服務器間的連接速度較慢的情況下。當需要訪問服務器的話,Javascript Web應用會明顯的感覺到慢。
最后,執行查詢需要倉庫和服務器端協同工作。在默認情況下,Ember Data將搜索條件作為HTTP請求的body發送到服務器端。如果服務器端並不支持查詢的格式,那么需要修改服務器以便支持查詢,或者通過自定義一個適配器來定義需要發送的查詢。
過濾器是對倉庫中緩存的對象執行實時的查詢。一旦新的記錄被加載到倉庫,過濾器會自動檢查記錄是否匹配條件,如果匹配,會將其加入到搜索結果的數組中。如果這個數組顯示在模板中,模板也會自動更新。
過濾器還會顧及新創建的還未保存的記錄,以及被修改未保存的記錄。如果希望記錄在客戶端一創建或者修改就在搜索結果中顯示,那么就應該是用過濾器。
請記住,如果倉庫不知道記錄的存在,記錄不會包含在過濾器中。通過倉庫的push()方法可以確保倉庫知道記錄的存在。
此外也有一個制約條件,就是有多少記錄可以保存在內存中進行過濾,直到出現性能問題。
最后,應該聯合使用查詢和過濾器來揚長避短。記住通過查詢服務器返回的記錄都是緩存在倉庫中的。可以利用這一點來做過濾,通過查詢倉庫來將記錄加載到倉庫,然后使用一個過濾函數來匹配相同的記錄。
這可以避免所有的記錄都通過服務器來查詢,同時也創建了一個能包含客戶端創建和修改的記錄的動態更新列表。
App.PostsFavoritedRoute = Ember.Route.extend({ model: function() { var store = this.store; // Create a filter for all favorited posts that will be displayed in // the template. Any favorited posts that are already in the store // will be displayed immediately; // Kick off a query to the server for all posts that // the user has favorited. As results from the query are // returned from the server, they will also begin to appear. return store.filter('post', { favorited: true }, function(post) { return post.get('isFavorited'); }); } });
如何將后台創建的記錄通知Ember Data?
當通過Ember Data的store.find
方法來請求一條記錄時,Ember會自動將數據加載到store
中。這樣Ember就避免了在之后在發起一個請求來獲取已經獲取到的記錄。此外,加載一條記錄到store
時,所有的包含該條記錄的RecordArray
都會被更新(例如store.filter
或者store.all
構造的)。這就意味着所有依賴與RecordArray
的數據綁定或者計算屬性都會在添加新的或者更新記錄值的時候自動進行同步。
而一些應用可能希望能不通過store.find
請求記錄來添加或者更新store
中得記錄。為了實現這種需求,可以通過使用DS.Store
的push
,pushPayload
,或者update
方法。這對於那些有一個通道(例如SSE或者Web Socket)通知應用后台有新記錄創建或者更新非常有用。
push是加載記錄到Ember Data的store
的最簡單方法。當使用push
時,一定要記住將JSON對象推入store
之前將其反序列化。push
一次只接受一條記錄。如果希望一次加載一組記錄到store
那么可以調用pushMany.
socket.on('message', function (message) { var type = store.modelFor(message.model); var serializer = store.serializerFor(type.typeKey); var record = serializer.extractSingle(store, type, message.data); store.push(message.model, record); });
pushPayload是一個store#push
方法的便利封裝,它將使用模型實現了pushPayload
方法的序列化對象來反序列化有效載荷。需要注意的是這個方法並不能與JSONSerializer
一同使用,因為其並沒有實現pushPayload
方法。
socket.on('message', function (message) { store.pushPayload(message.model, message.data); });
update與push
方法類似,不同的是其可以處理部分屬性,而不需要覆蓋整個記錄的屬性。這個方法對於只接收到記錄改變的屬性的通知的應用尤為有用。與push
方法一樣,update
需要在調用之前將JSON對象反序列化。
socket.on('message', function (message) { var hash = message.data; var type = store.modelFor(message.model); var fields = Ember.get(type, 'fields'); fields.forEach(function(field) { var payloadField = Ember.String.underscore(field); if (field === payloadField) { return; } hash[field] = hash[payloadField]; delete hash[payloadField]; }); store.push(message.model, hash); });
資源引用
https://github.com/emberjs/data