Ember model簡介

2018-01-06 17:59 更新

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.jsAPP:{}(大概第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)限問題。配置完之后需要重啟項目才能生效!

1,簡介

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ù)雜度。
對于modelEmber 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ù)。如果沒有Storemodel核心內(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最核心的幾個東西:modelsrecords、adapters、store。

2,核心概念

聲明:下面簡介內(nèi)摘抄至http://www.emberjs.cn/guides/models/#toc_

1,store

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ù)。

加載得到的數(shù)據(jù)

下面是我的firebase上的部分?jǐn)?shù)據(jù)截圖。

firebase數(shù)據(jù)

可以看到成功獲取到id-JzySrmbivaSSFG6WwOk的數(shù)據(jù)。更多關(guān)于數(shù)據(jù)的操作在后面會詳細(xì)介紹。

2,model

有關(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)系是一樣的。

3,record

recordmodel的實(shí)例,包含了從服務(wù)器端加載而來的數(shù)據(jù)。應(yīng)用本身也可以創(chuàng)建新的記錄,以及將新記錄保存到服務(wù)器端。

記錄由以下兩個屬性來唯一標(biāo)識:

  1. 模型類型
  2. 一個全局唯一的ID

比如前面的實(shí)例article就是通過find方獲取。獲取到的結(jié)果就是一個record。

4,adapter

適配器是一個了解特定的服務(wù)器后端的對象,主要負(fù)責(zé)將對記錄(record)的請求和變更轉(zhuǎn)換為正確的向服務(wù)器端的請求調(diào)用。

例如,如果應(yīng)用需要一個ID1person記錄,那么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)保存是否成功。

5,cache

store會自動緩存記錄。如果一個記錄已經(jīng)被加載了,那么再次訪問它的時候,會返回同一個對象實(shí)例。這樣大大減少了與服務(wù)器端的往返通信,使得應(yīng)用可以更快的為用戶渲染所需的UI。

例如,應(yīng)用第一次從store中獲取一個ID1person記錄時,將會從服務(wù)器端獲取對象的數(shù)據(jù)。

但是,當(dāng)應(yīng)用再次需要ID1person記錄時,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)獲取到的記錄就可以了。

3,架構(gòu)簡介

應(yīng)用第一次從store獲取一個記錄時,store會發(fā)現(xiàn)本地緩存并不存在一份被請求的記錄的副本,這時會向適配器發(fā)請求。適配器將從持久層去獲取記錄;通常情況下,持久層都是一個HTTP服務(wù),通過該服務(wù)可以獲取到記錄的一個JSON表示。

架構(gòu)圖1

如上圖所示,適配器有時不能立即返回請求的記錄。這時適配器必須向服務(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)用的承諾。

架構(gòu)圖2

下面將介紹一下當(dāng)store已經(jīng)緩存了請求的記錄時會發(fā)生什么。

架構(gòu)圖3

在這種情形下,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吧。您的肯定對我來說是最大的動力??!

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號