写点什么

Ember.js 项目开发之 Ember Data

用户头像
devpoint
关注
发布于: 2 小时前
Ember.js 项目开发之 Ember Data

Ember.js 是一个基于 MVVM 模型的开源框架,该框架主要用于创建复杂的多页面应用程序。它最大的特点是:持续推出最新的特性,并不会丢弃任何旧功能。


与大多数前端开发框架不同,使用Ember.js,必须遵循一套严格的 JS 体系结构,也就是说,Ember.js并不具备高度的灵活性。不过,正是得益于这套 JS 体系结构,Ember.js明显更加完善、稳定,可以使用其任意版本推出的工具与最新版本集成,却不必过分担忧兼容性问题。本文开始入门Ember.js项目开发之 Ember Data


Ember Data 是一套功能强大的工具,用于格式化请求,规范化响应以及有效地管理本地数据缓存。


Ember.js 本身可与任何类型的后端一起使用:RESTJSON:APIGraphQL 或其他后端类型。

什么是 Ember Data 模型?

Ember Data 中,模型是代表应用程序提供给用户的基础数据的对象。请注意,尽管 Ember Data 模型具有 model 相同的名称,但它与 Routes 方法的概念不同 。


不同的应用程序可能具有非常不同的模型,具体取决于它们要解决的问题。例如,照片共享应用程序可能具有一个 Photo 代表特定照片的模型,以及一个 PhotoAlbum 代表一组照片的模型。相比之下,在线购物应用程序将可能有不同的型号,像ShoppingCartInvoiceLineItem


模型往往是持久的。这意味着用户在关闭浏览器窗口时不会期望模型数据丢失。为确保没有数据丢失,如果用户对模型进行了更改,则需要将模型数据存储在不会丢失的位置。


通常,大多数模型是从使用数据库存储数据的服务器加载并保存到服务器的。通常,会将模型的 JSON 表示来回发送到已编写的 HTTP 服务器。但是,Ember 使用其他持久性存储变得容易,例如使用IndexedDB保存到用户的硬盘上,或者使避免编写和托管自己的服务器的托管存储解决方案。


从存储中加载模型后,组件便知道如何将模型数据转换为用户可以与之交互的 UI。有关组件如何获取模型数据的更多信息,请参见“ 指定路线的模型” 指南。


首先,使用 Ember Data 可能会感觉与 JavaScript 应用程序的方式有所不同。许多开发人员都熟悉使用 Ajax 从端点获取原始 JSON 数据,这乍看起来似乎很容易。但是,随着时间的流逝,复杂性会渗入应用程序代码中,从而使其难以维护。


与 Ember 数据,管理模型为应用程序的增长变得既简单和容易。


一旦了解了Ember Data,将拥有更好的方法来管理应用程序中数据加载的复杂性。这将使代码得以发展和壮大,并具有更好的可维护性。

Ember Data 的灵活性

由于使用了适配器模式,因此可以将 Ember Data 配置为与许多不同种类的后端一起使用。有一个完整的适配器生态系统和几个内置适配器 ,可让 Ember 应用程序与不同类型的服务器通信。


默认情况下,Ember Data 旨在与 JSON:API 一起使用。JSON:API 是用于构建常规,健壮且高性能的 API 的正式规范,该 API 允许客户端和服务器通信模型数据。


JSON:API 标准化了 JavaScript 应用程序与服务器的通信方式,因此可以减少前端和后端之间的耦合,并拥有更大的自由来更改堆栈。


如果需要将 Ember.js 应用程序与没有可用适配器的服务器集成在一起(例如,手动滚动了不遵循任何 JSON 规范的 API 服务器),则 Ember Data 可以进行配置以使其工作服务器返回的任何数据。


Ember Data 还旨在与流服务器一起使用,例如由 WebSockets 驱动的服务器。可以打开服务器的套接字,并在发生任何更改时将更改推送到 Ember Data 中,从而为应用程序提供始终保持最新状态的实时用户界面。

The Store and a Single Source of Truth

构建 Web 应用程序的一种常用方法是将用户界面元素与数据提取紧密耦合。例如,假设正在编写博客应用程序的 admin 部分,该部分具有列出当前登录用户的草稿的功能。


可能很想让该组件负责获取和存储数据:


import Component from '@glimmer/component';import { tracked } from '@glimmer/tracking';import fetch from 'fetch';
export default class ListOfDraftsComponent extends Component { @tracked drafts;
constructor() { super(...arguments);
fetch('/drafts').then(data => { this.drafts = data; }); }}
复制代码


然后,可以在组件模板中显示草稿列表,如下所示:


<ul>  {{#each this.drafts key="id" as |draft|}}    <li>{{draft.title}}</li>  {{/each}}</ul>
复制代码


这对于list-of-drafts组件非常有用。但是,应用程序可能由许多不同的组件组成。在另一页上,可能希望组件显示草稿数。可能会想将现有willRender代码复制并粘贴到新组件中。


import Component from '@glimmer/component';import { tracked } from '@glimmer/tracking';import fetch from 'fetch';
export default class DraftsButtonComponent extends Component { @tracked drafts;
constructor() { super(...arguments);
fetch('/drafts').then(data => { this.drafts = data; }); }}
复制代码


<LinkTo @route="drafts" @tagName="button">  Drafts ({{this.drafts.length}})</LinkTo>
复制代码


不幸的是,该应用程序现在将针对相同的信息发出两个单独的请求。冗余数据的获取不仅浪费带宽,而且影响应用程序的感知速度,而且代价不菲,而且这两个值很容易不同步。自己可能已经使用了 Web 应用程序,其中项目列表与工具栏中的计数器不同步,从而导致令人沮丧和不一致的体验。


应用程序的 UI 和网络代码之间也存在紧密的联系。如果 URL 或 JSON 有效负载的格式发生更改,则很可能会导致难以跟踪的方式破坏所有 UI 组件。


良好设计的 SOLID 原则告诉我们,对象应该承担单一责任。组件的责任应该是向用户呈现模型数据,而不是获取模型。


好的 Ember 应用程序采用了不同的方法。Ember Data 提供了一个 存储库,该存储库是应用程序中模型的中央存储库。路线及其相应的控制器可以向 store 询问模型,而 store 负责知道如何获取它们。


这也意味着 store 可以检测到两个不同的组件在请求相同的模型,从而使应用仅能从服务器获取一次数据。可以将 store 视为应用程序模型的通读缓存。路由及其相应的控制器都可以访问此共享存储;当他们需要显示或修改模型时,他们首先要向 store 询问。


Ember Data 在每个路由和控制器中都注入了存储服务,因此可以通过 this.store 来访问它!

Models

在 Ember Data 中,每个 Model 都由一个子类表示,该子类 Model 定义了提供给用户的数据的属性,关系和行为。


Model 定义了服务器将提供的数据类型。例如,一个 Person Model 可能具有一个 firstName 字符串 birthday 属性和一个日期属性:


import Model, { attr } from '@ember-data/model';
export default class PersonModel extends Model { @attr('string') firstName; @attr('date') birthday;}
复制代码


Model 还描述了它与其他对象的关系。例如,一个 order 可能有多个 line-items,而一个 line-item 可能属于一个特定的 order。


import Model, { hasMany } from '@ember-data/model';
export default class OrderModel extends Model { @hasMany('line-item') lineItems;}
复制代码


import Model, { belongsTo } from '@ember-data/model';
export default class LineItemModel extends Model { @belongsTo('order') order;}
复制代码


Model 本身没有任何数据,它们定义了特定实例的属性,关系和行为,称为 records。

Records

一个 record 是一个包含从服务器加载数据模型的实例。应用程序还可以创建新记录并将其保存回服务器。


record 由其模型类型和 ID 唯一标识。


例如,如果正在编写联系人管理应用程序,则可能有一个 Person 模型。应用中的单个记录可能具有的类型 person 和 ID 1 或 steve-buscemi。


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


第一次保存记录时,服务器通常会将 ID 分配给记录,但是也可以在客户端生成 ID。

Adapter

一个适配器是转换来自 Ember 请求(例如“找到为 1 的 ID 的用户”)转换请求到服务器的对象。


例如,如果应用程序要求 PersonID 为的 1,则 Ember 应该如何加载它?通过 HTTP 还是 WebSocket?如果是 HTTP,则是 URL /person/1还是/resources/people/1


适配器负责回答所有这些问题。每当应用程序向 store 询问尚未缓存的记录时,它都会向适配器询问。如果更改记录并保存,则存储会将记录移交给适配器,以将适当的数据发送到服务器,并确认保存成功。


适配器使完全更改 API 的实现方式,而不会影响 Ember 应用程序代码。

Caching

store 将自动缓存记录。如果已经加载了一条记录,那么第二次请求将始终返回相同的对象实例。这样可以最大程度地减少往返服务器的次数,并允许应用程序尽快将其 UI 呈现给用户。


例如,应用程序第一次要求 store 提供 person ID 为 1 的 记录时,它将从服务器中获取该信息。


但是,下次应用程序请求 person ID 为 1 时,store 将注意到它已经从服务器检索并缓存了该信息。它不会向其他应用发送相同信息的请求,而是为应用提供与第一次提供的记录相同的记录。此功能(无论查找多少次,总是返回相同的记录对象)有时被称为身份映射。


使用身份映射非常重要,因为它可以确保在 UI 的一部分中所做的更改会传播到 UI 的其他部分。这也意味着不必手动保持记录同步-可以按 ID 要求记录,而不必担心应用程序的其他部分是否已经请求并加载了它。


返回缓存记录的一个缺点是,可能会发现自将数据首次加载到 store 的身份映射以来,数据的状态已更改。为了防止这种过时的数据长期存在问题,每次从存储中返回缓存的记录时,Ember Data 都会在后台自动发出请求。当输入新数据时,将更新记录,并且如果自初始渲染以来记录发生了更改,则将使用新信息重新渲染模板。

架构概述

应用程序第一次要求 store 提供记录时,store 会发现它没有本地副本,并向适配器请求该副本。适配器将去往持久层中检索记录;通常,这将是从 HTTP 服务器提供的记录的 JSON 表示形式。



如上图所示,适配器不能总是立即返回请求的记录。在这种情况下,适配器必须向服务器发出异步请求,并且只有在该请求完成加载后,才能使用其备份数据创建记录。


由于这种异步性,存储立即从该方法返回一个 Promise findRecord()。同样,存储对适配器的任何请求也将返回 promise。


一旦对服务器的请求返回了请求记录的 JSON 负载,适配器将使用 JSON 解析返回给 store 的 promise。


然后,store 使用该 JSON,使用 JSON 数据初始化记录,并使用新加载的记录解析返回给应用程序的 Promise。



如果请求存储已经在其缓存中的记录,该怎么办。



在这种情况下,由于store已经知道该记录,因此它将返回一个promise,该promise将立即与该记录一起解决。由于它已经在本地保存了副本,因此无需向适配器(以及服务器)索要副本。

发布于: 2 小时前阅读数: 4
用户头像

devpoint

关注

细节的追求者 2011.11.12 加入

专注前端开发,用技术创造价值!

评论

发布
暂无评论
Ember.js 项目开发之 Ember Data