Ember官網(wǎng)用了大篇幅來介紹model
,相比之前的controller
簡直就是天壤之別??!
從本篇開始學(xué)習(xí)Ember的模型,這一章也是Ember基礎(chǔ)部分的最后一章內(nèi)容,非常的重要(不管你信不信反正我是信了)。
在開始學(xué)習(xí)model
之前先做好準(zhǔn)備工作:
重新創(chuàng)建一個Ember項目,仍舊使用的是Ember CLI命令創(chuàng)建。
ember new chapter6_models
cd chapter6_models
ember server
在瀏覽器執(zhí)行項目,看到如下信息說明項目搭建成功。 Welcome to Ember
本章演示所用到的代碼都可以從https://github.com/ubuntuvim/my_emberjs_code/tree/master/chapter6_models獲取。
在介紹model
之前先在項目中引入firebase。相關(guān)的配置教材請移步這里(如果無法加載頁面請先在https://www.firebase.com/注冊用戶)。firebase的官網(wǎng)提供了專門用于Ember的版本,還提供了非常簡單的例子。從安裝到整合都給出了非常詳細(xì)代碼教程。
下面是我的整合步驟(命令都是在項目目錄下執(zhí)行的):
ember install emberfire
安裝完成之后會自動創(chuàng)建adapter(app/adapters/application.js)
,對于這個文件不需要做任何修改,官網(wǎng)提供的代碼也許跟你的項目的代碼不同,應(yīng)該是官網(wǎng)的版本是舊版的。
config/environment.js
修改第八行firebase: 'https://YOUR-FIREBASE-NAME.firebaseio.com/'
。這個地址是你注冊用戶時候得到的。你可以從這里查看你的地址。比如下圖所示位置
config/enviroment.js
的APP:{}
(大概第20行)后面新增如下代碼
APP: {
// Here you can pass flags/options to your application instance
// when it is created
},
contentSecurityPolicy: {
'default-src': "'none'",
'script-src': "'self' 'unsafe-inline' 'unsafe-eval' *",
'font-src': "'self' *",
'connect-src': "'self' *",
'img-src': "'self' *",
'style-src': "'self' 'unsafe-inline' *",
'frame-src': "*"
}
然后再注釋掉第7行原有屬性(安裝firebase
自動生成的,但是配置不夠完整):contentSecurityPolicy
。
或者你可以參考我的配置文件:
/* jshint node: true */
module.exports = function(environment) {
var ENV = {
modulePrefix: 'chapter6-models',
environment: environment,
// contentSecurityPolicy: { 'connect-src': "'self' https://auth.firebase.com wss://*.firebaseio.com" },
firebase: '你的firebase連接',
baseURL: '/',
locationType: 'auto',
EmberENV: {
FEATURES: {
// Here you can enable experimental features on an ember canary build
// e.g. 'with-controller': true
}
},
APP: {
// Here you can pass flags/options to your application instance
// when it is created
},
contentSecurityPolicy: {
'default-src': "'none'",
'script-src': "'self' 'unsafe-inline' 'unsafe-eval' *",
'font-src': "'self' *",
'connect-src': "'self' *",
'img-src': "'self' *",
'style-src': "'self' 'unsafe-inline' *",
'frame-src': "*"
}
};
// 其他代碼省略……
return ENV;
};
如果不做這個配置啟動項目之后瀏覽器會提示一堆的錯誤。主要是一些訪問權(quán)限問題。配置完之后需要重啟項目才能生效!
model
是一個用于向用戶呈現(xiàn)底層數(shù)據(jù)的對象。不同的應(yīng)用有不同的model
,這取決于解決的問題需要什么樣的model
就定義什么樣的model
。
model
通常是持久化的。這也就意味著用戶關(guān)閉了瀏覽器窗口model
數(shù)據(jù)不應(yīng)該丟失。為了確保model
數(shù)據(jù)不丟失,你需要存儲model
數(shù)據(jù)到你所指定的服務(wù)器或者是本地數(shù)據(jù)文件中。
一種非常常見的情況是,model
數(shù)據(jù)會以JSON
的格式通過HTTP
發(fā)送到服務(wù)器并保存在服務(wù)中。Ember還未開發(fā)者提供了一種更加簡便的方式:使用IndexedDB(使用在瀏覽器中的數(shù)據(jù)庫)。這種方式是把model
數(shù)據(jù)保存到本地?;蛘呤褂?a rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" >Ember Data,又或者使用firebase,把數(shù)據(jù)直接保存到遠(yuǎn)程服務(wù)器上,后續(xù)的文章我將引入firebase,把數(shù)據(jù)保存到遠(yuǎn)程服務(wù)器上。
Ember使用適配器模式連接數(shù)據(jù)庫,可以適配不同類型的后端數(shù)據(jù)庫而不需要修改任何的網(wǎng)絡(luò)代碼。你可以從emberobserver上看到幾乎所有Ember支持的數(shù)據(jù)庫。
如果你想把你的Ember應(yīng)用與你的遠(yuǎn)程服務(wù)器整合,幾遍遠(yuǎn)程服務(wù)器API
返回的數(shù)據(jù)不是規(guī)范的JSON
數(shù)據(jù)也不要緊,Ember Data可以配置任何服務(wù)器返回的數(shù)據(jù)。
Ember Data還支持流媒體服務(wù)器,比如WebSocket。你可以打開一個socket連接遠(yuǎn)程服務(wù)器,獲取最新的數(shù)據(jù)或者把變化的數(shù)據(jù)推送到遠(yuǎn)程服務(wù)器保存。
Ember Data為你提供了更加簡便的方式操作數(shù)據(jù),統(tǒng)一管理數(shù)據(jù)的加載,降低程序復(fù)雜度。
對于model
與Ember Data的介紹就到此為止吧,官網(wǎng)用了大量篇幅介紹Model,在此我就不一一寫出來了!太長了,寫出來也沒人看的?。?!如果有興趣自己看吧!點(diǎn)擊查看詳細(xì)信息。
下面先看一個簡單的例子,由這個例子延伸出有關(guān)于model
的核心概念。這些代碼是舊版寫法,僅僅是為了說明問題,本文也不會真正執(zhí)行。
// app/components/list-of-drafts.js
export default Ember.Component.extend({
willRender() {
// ECMAScript 6語法
$.getJSON('/drafts').then(data => {
this.set('drafts', data);
});
}
});
定義了一個組件類。并在組件類中獲取json
格式數(shù)據(jù)。
下面是組件對應(yīng)的模板文件。
<ul>
{{#each drafts key="id" as |draft|}}
<li>{{draft.title}}</li>
{{/each}}
</ul>
再定義另外一個組件類和模板
// app/components/list-button.js
export default Ember.Component.extend({
willRender() {
// ECMAScript 6語法
$.getJSON('/drafts').then(data => {
this.set('drafts', data);
});
}
});
{{#link-to ‘drafts’ tagName=’button’}}
Drafts ({{drafts.length}})
{{/link-to}}
組件list-of-drafts
類和組件list-button
類是一樣的,但是他們的對應(yīng)的模板卻不一樣。但是都是從遠(yuǎn)程服務(wù)器獲取同樣的數(shù)據(jù)。如果沒有Store
(model
核心內(nèi)容之一)那么每次這兩個模板渲染都會是組件類調(diào)用一次遠(yuǎn)程數(shù)據(jù)。并且返回的數(shù)據(jù)是一樣的。這無形中增加了不必要的請求,暫用了不必要的寬帶,用戶體驗(yàn)也不好。但是有了Store
就不一樣了,你可以把Store
理解為倉庫,每次執(zhí)行組件類時先到Store
中獲取數(shù)據(jù),如果沒有再去遠(yuǎn)程獲取。當(dāng)在其中一個組件中改變某些數(shù)據(jù),數(shù)據(jù)的更改也能理解反應(yīng)到另一個獲取此數(shù)據(jù)的組件上(與計算屬性自動更新一樣),而這個組件不需要再去服務(wù)請求才能獲取最新更改過的數(shù)據(jù)。
下面的內(nèi)容將為你一一介紹Ember Data最核心的幾個東西:models
、records
、adapters
、store
。
聲明:下面簡介內(nèi)摘抄至http://www.emberjs.cn/guides/models/#toc_。
store
是應(yīng)用存放記錄的中心倉庫。你可以認(rèn)為store
是應(yīng)用的所有數(shù)據(jù)的緩存。應(yīng)用的控制器和路由都可以訪問這個共享的store
;當(dāng)它們需要顯示或者修改一個記錄時,首先就需要訪問store
。
DS.Store
的實(shí)例會被自動創(chuàng)建,并且該實(shí)例被應(yīng)用中所有的對象所共享。
store
可以看做是一個緩存。在下面的cache
會結(jié)合store
介紹。
下面的例子結(jié)合firebase演示:
創(chuàng)建路由和model
:
ember g route store-example
ember g model article
// app/models/article.js
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string'),
body: DS.attr('string'),
timestamp: DS.attr('number'),
category: DS.attr('string')
});
這個就是model
,本章要講的內(nèi)容就是它!為何沒有定義id屬性呢?Ember
會默認(rèn)生成id
屬性。
我們在路由的model
回調(diào)中獲取遠(yuǎn)程的數(shù)據(jù),并顯示在模板上。
// app/routes/store-example.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
// 從store中獲取id為JzySrmbivaSSFG6WwOk的數(shù)據(jù),這個數(shù)據(jù)是我在我的firebase中初始化好的
return this.store.find('article', '-JzySrmbivaSSFG6WwOk');
}
});
find
方法的第一個參數(shù)是model
類名,第二個參數(shù)對象的id
屬性值。記得id屬性不需要在model
類中手動定義,Ember會自動為你定義。
<h2>{{model.title}}</h2>
<div class="body">
{{model.body}}
</div>
頁面加載之后可以看到獲取到的數(shù)據(jù)。
下面是我的firebase上的部分?jǐn)?shù)據(jù)截圖。
可以看到成功獲取到id
為-JzySrmbivaSSFG6WwOk
的數(shù)據(jù)。更多關(guān)于數(shù)據(jù)的操作在后面會詳細(xì)介紹。
有關(guān)model
的概念前面的簡介已經(jīng)介紹了,這里不再贅述。
model
定義:
model
是由若干個屬性構(gòu)成的。attr
方法的參數(shù)指定屬性的類型。
export default DS.Model.extend({
title: DS.attr('string'), // 字符串類型
flag: DS.attr('boolean'), // 布爾類型
timestamp: DS.attr('number'), // 數(shù)字類型
birth: DS.attr(‘date’) //日期類型
});
模型也聲明了它與其他對象的關(guān)系。例如,一個Order
可以有許多LineItems
,一個LineItem
可以屬于一個特定的Order
。
App.Order = DS.Model.extend({
lineItems: DS.hasMany('lineItem')
});
App.LineItem = DS.Model.extend({
order: DS.belongsTo('order')
});
這個與數(shù)據(jù)的表之間的關(guān)系是一樣的。
record
是model
的實(shí)例,包含了從服務(wù)器端加載而來的數(shù)據(jù)。應(yīng)用本身也可以創(chuàng)建新的記錄,以及將新記錄保存到服務(wù)器端。
記錄由以下兩個屬性來唯一標(biāo)識:
比如前面的實(shí)例article
就是通過find
方獲取。獲取到的結(jié)果就是一個record
。
適配器是一個了解特定的服務(wù)器后端的對象,主要負(fù)責(zé)將對記錄(record
)的請求和變更轉(zhuǎn)換為正確的向服務(wù)器端的請求調(diào)用。
例如,如果應(yīng)用需要一個ID
為1
的person
記錄,那么Ember Data是如何加載這個對象的呢?是通過HTTP,還是Websocket?如果是通過HTTP,那么URL會是/person/1
,還是/resources/people/1
呢?
適配器負(fù)責(zé)處理所有類似的問題。無論何時,當(dāng)應(yīng)用需要從store
中獲取一個沒有被緩存的記錄時,應(yīng)用就會訪問適配器來獲取這個記錄。如果改變了一個記錄并準(zhǔn)備保存改變時,store
會將記錄傳遞給適配器,然后由適配器負(fù)責(zé)將數(shù)據(jù)發(fā)送給服務(wù)器端,并確認(rèn)保存是否成功。
store
會自動緩存記錄。如果一個記錄已經(jīng)被加載了,那么再次訪問它的時候,會返回同一個對象實(shí)例。這樣大大減少了與服務(wù)器端的往返通信,使得應(yīng)用可以更快的為用戶渲染所需的UI。
例如,應(yīng)用第一次從store
中獲取一個ID
為1
的person
記錄時,將會從服務(wù)器端獲取對象的數(shù)據(jù)。
但是,當(dāng)應(yīng)用再次需要ID
為1
的person
記錄時,store
會發(fā)現(xiàn)這個記錄已經(jīng)獲取到了,并且緩存了該記錄。那么store
就不會再向服務(wù)器端發(fā)送請求去獲取記錄的數(shù)據(jù),而是直接返回第一次時候獲取到并構(gòu)造出來的記錄。這個特性使得不論請求這個記錄多少次,都會返回同一個記錄對象,這也被稱為Identity Map
(標(biāo)識符映射)。
使用標(biāo)識符映射非常重要,因?yàn)檫@樣確保了在一個UI上對一個記錄的修改會自動傳播到UI其他使用到該記錄的UI。同時這意味著你無須手動去保持對象的同步,只需要使用ID
來獲取應(yīng)用已經(jīng)獲取到的記錄就可以了。
應(yīng)用第一次從store
獲取一個記錄時,store
會發(fā)現(xiàn)本地緩存并不存在一份被請求的記錄的副本,這時會向適配器發(fā)請求。適配器將從持久層去獲取記錄;通常情況下,持久層都是一個HTTP服務(wù),通過該服務(wù)可以獲取到記錄的一個JSON
表示。
如上圖所示,適配器有時不能立即返回請求的記錄。這時適配器必須向服務(wù)器發(fā)起一個異步的請求,當(dāng)請求完成加載后,才能通過返回的數(shù)據(jù)創(chuàng)建的記錄。
由于存在這樣的異步性,store
會從find()
方法立即返回一個承諾(promise
)。另外,所有請求需要store
與適配器發(fā)生交互的話,都會返回承諾。
一旦發(fā)給服務(wù)器端的請求返回被請求記錄的JSON數(shù)據(jù)時,適配器會履行承諾,并將JSON
傳遞給store
。
store
這時就獲取到了JSON
,并使用JSON
數(shù)據(jù)完成記錄的初始化,并使用新加載的記錄來履行已經(jīng)返回到應(yīng)用的承諾。
下面將介紹一下當(dāng)store
已經(jīng)緩存了請求的記錄時會發(fā)生什么。
在這種情形下,store
已經(jīng)緩存了請求的記錄,不過它也將返回一個承諾,不同的是,這個承諾將會立即使用緩存的記錄來履行。此時,由于store
已經(jīng)有了一份拷貝,所以不需要向適配器去請求(沒有與服務(wù)器發(fā)生交互)。
models
、records
、adapters
、store
是你必須要理解的概念。這是Ember Data最核心的東西。
有關(guān)于上述的概念將會在后面的文章一一用代碼演示。理解了本文model
這一整章的內(nèi)容都不是問題了?。?!
博文完整代碼放在Github(博文經(jīng)過多次修改,博文上的代碼與github代碼可能有出入,不過影響不大?。?,如果你覺得博文對你有點(diǎn)用,請在github項目上給我點(diǎn)個star
吧。您的肯定對我來說是最大的動力??!
更多建議: