#1 介绍

来源:互联网 发布:got it get it gotcha 编辑:程序博客网 时间:2024/06/08 00:36

英文原版:https://guides.emberjs.com/v2.14.0/models/

模型(Model)是一个代表应用提供给用户的基础数据的对象。不同应用基于要面对的问题提供不同的模型。

比如,一个用来分享照片的应用或许需要一个Photo模型用来代表照片,一个PhotoAlbum模型用来代表一组照片。于此形成鲜明对比的是,一个在线的购物应用会需要若干个不同的模型,比如购物车、票据、购买项等。

模型往往是被持久化的。这意味着用户不希望在关掉浏览器窗口后丢失数据,为了保证不丢失数据,当用户改变了模型数据后,你需要把模型数据保存起来,使得数据不会丢失。

典型的,几乎所有的模型数据的读取和保存都会在远程的数据库中。一般你会从一个远程的HTTP服务器发送/接收一个代表模型的JSON数据。然而,Ember使你很方便的使用更合适的存储,比如将数据保存到用户端的IndexedDB上,或者使用其他的托管存储方案来避免频繁的读写远程服务器。

一旦你从存储读取了模型,那么组件会知道如何将模型数据翻译到UI界面中来让用户进行交互操作。想了解更多的关于组件如何获取模型数据,请移步至Specifying a Route’s Model 教程。

Ember Data, 默认的会在你创建应用的时候就存在, 它是一个可以帮你轻松检索从服务端返回的JSON、保存更新到服务器,创建新的模型,并且与Ember有紧密联系的库。

对亏了它使用的是适配器模式,Ember Data可以通过配置来满足不同的后段程序。这里有一个完整的适配器生态系统,它可以使得你的Ember应用在不编写额外的通信代码的同时对接不同类型的服务端。

如果你需要将你的Ember应用和一个没有配置适配器的服务端相连接,那么Ember Data可以帮助你,它被设计为可配置的并且可以对接从服务端返回的任何类型的数据。

Ember Data同样被设计可对接流媒体服务器,比如webSockets。你可以开启一个socket,并且将变化的数据推入Ember Data, 同时提供一个实时的用户接口。

刚开始的时候,使用Ember Data可能会感觉有点不适应。很多开发者都习惯通过AJAX从服务器获取JSON数据,这样做比较容易上手。随着时间的推移,随着项目越来越复杂,你就会发现项目变的不好维护了。

通过Ember Data,对模型数据的管理不会随着项目复杂而难维护。

一旦你理解了Ember Data, 你就会觉得,面对日益复杂的数据其实也没那么焦头烂额。 它会使你的代码避免成为一团乱麻。

存储和单一数据源

建立应用的一个通常的做法是将用户接口元素与数据紧密的联系在一起。比如,假设你正在编写一个博客应用的管理员功能部分,它有个功能是罗列出当前登录的用户的基本信息。

你试图让组件负责获取和存储数据:

app/components/list-of-drafts.jsimport Ember from 'ember';export default Ember.Component.extend({  willRender() {    $.getJSON('/drafts').then(data => {      this.set('drafts', data);    });  }});

随后,你可以将数据通过组件的模版展示出来:

app/templates/components/list-of-drafts.hbs<ul>  {{#each drafts key="id" as |draft|}}    <li>{{draft.title}}</li>  {{/each}}</ul>

组件的功能目前还是挺正常的。然而,你的应用貌似是由很多不同组件组成的。在另一个页面,你打算用另一个组件来展示基本信息的数量。这时候,你可以会把上面的list-of-drafts组件中willRender中的代码复制过来:

app/components/drafts-button.jsimport Ember from 'ember';export default Ember.Component.extend({  willRender() {    $.getJSON('/drafts').then(data => {      this.set('drafts', data);    });  }});
app/templates/components/drafts-button.hbs{{#link-to 'drafts' tagName="button"}}Drafts ({{drafts.length}}){{/link-to}}

不幸的是,你的应用现在会发两个相同的请求。这样不仅会额外占用网络带宽,而且会降低应用的效率,并且这两次请求的返回值的内容很有可能不同步。这样会带来一个不太好的用户体验。

同时,这样实现的UI和代码之间存在紧耦合。如果JSON格式变化,那么产生比较难排查的问题。

好的设计风格告诉我们应该遵守指责单一原则。组件的指责仅仅就是将模型数据呈现给用户,而不是请求远端的模型。

优秀的Ember应用有不同的实现方式。Ember Data为你的应用提供了一个单例的中央仓库。路由和组件都可以向这个仓库索要模型,仓库来负责请求远端模型。

这也意味着当你有2个组件需要同一种模型的时候,store就会作出判断只让应用发出一次请求来获取这个模型。你可以认为store是一个为你的应用存放模型的缓存。只要路由或组件需要数据,它们首先会向store索取数据。

通过JSON API配置约定

通过基于Ember的约定,你可以显著减少大量不必要的代码。如果这些约定广泛的被团队的成员所熟知,那么这会使得队员之间的代码变的好理解。

相对于临时制定一些不那么确切的约定,Ember Data 的设计遵循着JSON API的规则。JSON API是一个正式的,公认的,并且有效的接口形式,通过这种形式可以使得客户端和服务端有着相同的通信准则。

JSON API 制定了javascript应用与服务端的通信标准,所以减少了前端与后段的耦合度,并且赋予你一定的自由空间。

作为类比,JSON API对于Ember应用和接口服务器,就如同SQL语句对于后段框架和数据库。

框架从来不需要写大量自定义代码来支持一个新的数据库;只要这个数据库支持SQL语句,那么对它添加一些支持是很容易的。

JSON API同样也具有这样的优势。前后端通过JSON API来交互,你可以在不改变前端代码的前提下对后段做大量的调整。并且就算你的应用跨平台了,比如即有IOS也有Android,你也能过通过JSON API的支持库继续使用你的Ember应用。

模型( Models )

在Ember Data中,每一个模型都是Model类的子类,它里面定义了关于数据中包含的属性,关系和行为等内容,这些内容最终通过实际的数据呈现给用户。

模型定义了从服务端返回的数据的类型。比如,一个Person模型或许包含有firstName属性和birthday属性。

app/models/person.jsimport DS from 'ember-data';export default DS.Model.extend({  firstName: DS.attr('string'),  birthday:  DS.attr('date')});

模型同时也描述了它与其他对象之间的关系。比如,一个order或许包含多个line-item, 一个line-item属于某个order。

app/models/order.jsimport DS from 'ember-data';export default DS.Model.extend({  lineItems: DS.hasMany('line-item')});
app/models/line-item.jsimport DS from 'ember-data';export default DS.Model.extend({  order: DS.belongsTo('order')});

模型并不包含数据本身,它仅仅定义某个特定实例的属性,关系和行为,这个特定的实例被成为记录–records

记录 (Records)

一条记录是一个带有返回数据的模型实例。当然,在应用中你也可以创建一条记录,然后返回给服务端。

一条记录被它的type和ID唯一标示。

比如,你正编写一个管理通讯录的应用,你或许定义了一个Person模型。一条单独的记录它的类型或许可以定义为person并且ID可以是1或者是steve-buscemi。

this.get('store').findRecord('person', 1); // => { id: 1, name: 'steve-buscemi' }

ID一般会在该条数据第一次被存入数据库的时候被赋予。不过,你也可以在客户端生成ID。

适配器(Adapter)

适配器的职责是把由Ember发送的请求翻译为服务器可以接受的形式。

比如,如果你的应用现在要获取一个ID为1的Person记录,Ember如何读取它?是通过HTTP还是WebSocket?如果是HTTP,那么请求的URL是/person/1还是/resources/people/1?

适配器可以帮你解决这些困惑。无论何时当应用向store索要一条没有被缓存的数据,store就会通过适配器来查询这条数据。如果你改变了这条数据并且保存了它,那么store就会将数据交给适配器,接着适配器会将必要的数据发送给服务器,并且会确认这条数据被成功的保存了。

适配器可以实现不论你Ember 应用的代码怎么变,都不会对交互API产生影响。

缓存(Caching)

store会自动的帮你把数据缓存下来。如果一条记录之前已经被读取过了,那么之后在获取这条数据时总是会优先从缓存中返回这条数据。这最大程度的减少了前后台交互时的资源浪费,并且使你的应用尽可能快的渲染UI。

比如,你的应用第一次请求获取了ID为1的Person记录,这时数据会从服务器返回。

然而,下一次你再获取ID为1的Person数据时,store会发现这条数据已经被缓存过来。那么store将会从缓存中直接取出这条数据。这也实现了,如果你应用的其他部分之前已经请求过这条数据,而在其他部分想再一次获取这条数据时,将不会再发请求。

一个从缓存中返回数据的负面影响是,你可能会发现一些带有状态的数据从第一次获得至今有些变化。但是缓存中的状态没有改变。为了防止陈旧的数据不被更新,Ember Data将在每次从缓存中取数据时暗地里发送一条请求。当服务端的数据已经被更新时,那么这些更新也会同步到缓存中。同时也会刷新模版。

架构总揽

当你的应用第一次请求某条数据时,store会发现本地缓存中没有这条数据,然后转身就想适配器索取数据。适配器将会从持久层中查询数据;典型的,数据将会以JSON形式从HTTP服务器返回。
这里写图片描述
如上面的插图所示,适配器不是总能立即返回被请求的记录。在这种球框下,适配器必须使用异步请求。并且只有在被请求的数据被加载完毕后一条记录才会被创建。

由于是异步的,store会通过find( )立即返回一个promise对象。相似的,任何store对适配器的请求也会立即收到一个promise对象。

一旦服务器返回了一个该条记录的JSON数据,适配器会立即resolve这个promise对象,并且将JSON数据传递给store。

接着store收到JSON数据,并初始化一条记录,下一步便会resolve返回给应用的promise对象,并将数据传过去。
这里写图片描述

我们来看一下,如果被请求的记录已经存在在缓存中时会发生什么。
这里写图片描述

这种情况下,由于store已经知道本地有这条数据,那么他会立即返回一个promise并同时resolve它。并且store不会询问适配器。

模型,记录,适配器和仓库(store)是Ember Data的核心概念。接下来的章节会更加深入的阐述这些概念,并且会展示如何搭配使用它们

本节完

原创粉丝点击