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