Spec-Zone.ru › Ember.js 2

Введение

Модели — это объекты, представляющие базовые данные, которые ваше приложение отображает пользователю. Разные приложения будут иметь очень разные модели, в зависимости от решаемых ими задач.

Например, приложение для обмена фотографиями может иметь модель Photo для представления конкретной фотографии и модель PhotoAlbum для представления группы фотографий. В отличие от этого, в приложении для онлайн-шопинга, вероятно, будут использоваться другие модели, например, ShoppingCart, Invoice или LineItem.

Модели, как правило, являются персистентными. Это означает, что пользователь не ожидает потери данных модели при закрытии окна браузера. Для того чтобы убедиться, что данные не потерялись, если пользователь внес изменения в модель, необходимо сохранить данные модели в месте, где они не будут утеряны.

Обычно большинство моделей загружаются и сохраняются на сервере, который использует базу данных для хранения данных. Обычно вы будете отправлять JSON-представления моделей туда и обратно на HTTP-сервер, который вы написали. Тем не менее, Ember позволяет легко использовать другие средства долговременного хранения, например, сохранение на жестком диске пользователя с помощью IndexedDB или облачные хранилища, которые позволяют вам избежать написания и размещения собственных серверов.

После загрузки моделей из хранилища компоненты знают, как преобразовать данные модели в пользовательский интерфейс, с которым может взаимодействовать ваш пользователь. Дополнительную информацию о том, как компоненты получают данные модели, см. в руководстве Определения модели маршрута.

Ember Data, включённая по умолчанию при создании нового приложения, — это библиотека, которая тесно интегрирована с Ember, чтобы легко извлекать модели с вашего сервера в формате JSON, сохранять обновления на сервере и создавать новые модели в браузере.

Благодаря использованию паттерна адаптера, Ember Data может быть настроена для работы со многими различными бэкендами. Существует целая экосистема адаптеров, которая позволяет вашему приложению Ember взаимодействовать с разными типами серверов, не прибегая к написанию кода для сетевых операций.

Если вам необходимо интегрировать ваше приложение Ember.js с сервером, для которого недоступен адаптер (например, вы вручную разработали API-сервер, который не соответствует ни одному JSON-спецификации), Ember Data разработана таким образом, чтобы её можно было настроить для работы с любыми данными, возвращаемыми вашим сервером.

Ember Data также разработана для работы с потоковыми серверами, такими как сервера, работающие на основе WebSockets. Вы можете открыть сокет на вашем сервере и передавать изменения в Ember Data при их возникновении, предоставляя вашему приложению пользовательский интерфейс в реальном времени, который всегда обновлен.

Сначала использование Ember Data может отличаться от того, как вы привыкли писать JavaScript-приложения. Многие разработчики знакомы с использованием AJAX для получения сырых данных JSON из конечной точки, что может показаться простым на первый взгляд. Однако со временем сложность просачивается в код вашего приложения, делая его трудным для поддержки.

С Ember Data управление моделями по мере роста вашего приложения становится как простым, так и лёгким.

После того, как вы поймёте Ember Data, у вас будет гораздо лучший способ управлять сложностью загрузки данных в вашем приложении. Это позволит вашему коду развиваться, не превращаясь в беспорядок.

Хранилище и единый источник истины

Один из распространённых способов создания веб-приложений — это тесная связь элементов пользовательского интерфейса с извлечением данных. Например, представьте, что вы пишете раздел администрирования приложения для ведения блогов, у которого есть функция, отображающая черновики для текущего вошедшего пользователя.

Вы можете быть искушены, чтобы сделать компонент ответственным за извлечение этих данных и их хранение:

import Component from '@ember/component';

export default Component.extend({
  willRender() {
    $.getJSON('/drafts').then(data => {
      this.set('drafts', data);
    });
  }
});

Затем вы можете отобразить список черновиков в шаблоне компонента следующим образом:

<ul>
  {{#each drafts key="id" as |draft|}}
    <li>{{draft.title}}</li>
  {{/each}}
</ul>

Это отлично работает для компонента list-of-drafts. Однако ваше приложение, скорее всего, состоит из многих различных компонентов. На другой странице вы можете захотеть компонент для отображения количества черновиков. Вы можете быть искушены скопировать и вставить ваш существующий код willRender в новый компонент.

import Component from '@ember/component';

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

К сожалению, приложение теперь будет делать два отдельных запроса к одной и той же информации. Не только избыточное извлечение данных дорогостояще с точки зрения затраченной пропускной способности и влияния на воспринимаемую скорость вашего приложения, но и легко допустить несоответствие двух значений. Вы, вероятно, сами пользовались веб-приложением, где список элементов не синхронизируется со счётчиком в строке меню, что приводит к раздражению и несогласованному опыту.

Также существует тесная связь между пользовательским интерфейсом вашего приложения и кодом сетевых операций. Если URL-адрес или формат JSON-payload меняется, это может привести к выходу из строя всех ваших компонентов пользовательского интерфейса такими способами, которые трудно отследить.

Принципы SOLID хорошего дизайна говорят нам, что объекты должны иметь одну ответственность. Ответственность компонента должна заключаться в представлении данных модели пользователю, а не в получении модели.

Хорошие приложения Ember используют другой подход. Ember Data предоставляет вам одно центральное хранилище (store) — центральный репозиторий моделей в вашем приложении. Маршруты и соответствующие контроллеры могут запрашивать модели у хранилища, и хранилище отвечает за знание того, как их получить.

Это также означает, что хранилище может определить, что два разных компонента запрашивают одну и ту же модель, что позволяет вашему приложению загружать данные с сервера только один раз. Вы можете рассматривать хранилище как кеш с просмотром для моделей вашего приложения. К этому общему хранилищу имеют доступ как маршруты, так и соответствующие контроллеры; когда им нужно отобразить или изменить модель, они сначала обращаются к хранилищу.

Конвенции вместо конфигурации с JSON API

Вы можете значительно уменьшить количество кода, который вам нужно написать и поддерживать, полагаясь на соглашения Ember. Поскольку эти соглашения будут общими для разработчиков в вашей команде, их соблюдение приводит к более легко поддерживаемому и понятному коду.

Вместо создания произвольного набора соглашений, Ember Data разработана для работы «из коробки» с JSON API. JSON API — это формальная спецификация для создания стандартных, надёжных и эффективных API, позволяющих клиентам и серверам обмениваться данными модели.

JSON API стандартизирует взаимодействие JavaScript-приложений с серверами, что снижает зависимость между вашим front-end и back-end и предоставляет вам больше свободы для изменения компонентов стека.

В качестве аналогии, JSON API для JavaScript-приложений и API-серверов то же, что и SQL для фреймворков на стороне сервера и баз данных. Такие популярные фреймворки, как Ruby on Rails, Laravel, Django, Spring и другие работают «из коробки» со многими различными базами данных, такими как MySQL, PostgreSQL, SQL Server и другими.

Фреймворкам (или приложениям, построенным на этих фреймворках) не нужно писать много пользовательского кода для добавления поддержки новой базы данных; поскольку эта база данных поддерживает SQL, добавление поддержки для неё относительно просто.

Точно так же и с JSON API. Используя JSON API для взаимодействия между вашим приложением Ember и вашим сервером, вы можете полностью изменить свой бэкенд, не вызывая ошибок в вашем фронтенде. И по мере добавления приложений для других платформ, таких как iOS и Android, вы сможете использовать библиотеки JSON API для этих платформ, чтобы легко использовать тот же API, что и ваше приложение Ember.

Модели

В Ember Data каждая модель представлена подклассом Model, который определяет атрибуты, отношения и поведение данных, которые вы предоставляете пользователю.

Модели определяют тип данных, которые будут предоставлены вашим сервером. Например, модель Person может иметь атрибут firstName, который является строкой, и атрибут birthday, который является датой:

import DS from 'ember-data';

export default DS.Model.extend({
  firstName: DS.attr('string'),
  birthday:  DS.attr('date')
});

Модель также описывает свои отношения с другими объектами. Например, order может иметь множество line-items, а line-item может принадлежать к конкретной order.

import DS from 'ember-data';

export default DS.Model.extend({
  lineItems: DS.hasMany('line-item')
});
import DS from 'ember-data';

export default DS.Model.extend({
  order: DS.belongsTo('order')
});

Модели сами по себе не содержат данных; они определяют атрибуты, отношения и поведение отдельных экземпляров, которые называются записью.

Записи

Запись — это экземпляр модели, содержащей данные, загруженные с сервера. Ваше приложение также может создавать новые записи и сохранять их на сервере.

Запись однозначно идентифицируется типом модели и ID.

Например, если бы вы писали приложение для управления контактами, у вас могла бы быть модель Person . Отдельная запись в вашем приложении могла бы иметь тип person и ID 1 или steve-buscemi.

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

ID обычно назначается записи сервером при первом сохранении, но вы также можете генерировать ID на стороне клиента.

Адаптер

Адаптер — это объект, который преобразует запросы от Ember (например, "найти пользователя с ID 1") в запросы к серверу.

Например, если ваше приложение запросило Person с ID 1, как Ember должен его загрузить? По протоколу HTTP или через WebSocket? Если это HTTP, то URL /person/1 или /resources/people/1?

Адаптер отвечает на все эти вопросы. Всякий раз, когда ваше приложение запрашивает у хранилища запись, которой нет в кэше, оно обращается к адаптеру за ней. Если вы изменяете запись и сохраняете её, хранилище передаст запись адаптеру, чтобы он отправил соответствующие данные на ваш сервер и подтвердил, что сохранение прошло успешно.

Адаптеры позволяют вам полностью изменить реализацию вашего API, не затрагивая код вашего приложения Ember.

Кэширование

Хранилище автоматически кэширует записи для вас. Если запись уже была загружена, повторный запрос всегда вернёт тот же экземпляр объекта. Это минимизирует количество обращений к серверу и позволяет вашему приложению отображать пользовательский интерфейс как можно быстрее.

Например, в первый раз, когда ваше приложение запрашивает у хранилища person запись с идентификатором 1, оно получит эту информацию с вашего сервера.

Однако, в следующий раз, когда приложение запросит person с идентификатором 1, хранилище заметит, что уже извлекло и кэшировало эту информацию с сервера. Вместо отправки нового запроса за той же информацией, оно предоставит вашему приложению ту же запись, что и в первый раз. Эта функция — всегда возвращать тот же объект записи, независимо от того, сколько раз вы её запрашиваете — иногда называется картой идентичности.

Использование карты идентичности важно, потому что оно гарантирует, что изменения, внесённые в одной части пользовательского интерфейса, распространяются на другие части. Это также означает, что вам не нужно вручную синхронизировать записи — вы можете запросить запись по идентификатору и не беспокоиться о том, запросили ли другие части приложения и загрузили ли её.

Один из недостатков возврата кэшированной записи заключается в том, что состояние данных может измениться с момента их первоначальной загрузки в карту идентичности хранилища. Чтобы предотвратить проблемы с устаревшими данными, Ember Data автоматически выполняет запрос в фоновом режиме каждый раз, когда кэшированная запись возвращается из хранилища. Когда новые данные поступают, запись обновляется, и если с момента первоначального отображения произошли изменения в записи, шаблон перерисовывается с новой информацией.

Обзор архитектуры

В первый раз, когда ваше приложение запрашивает запись у хранилища, хранилище видит, что не имеет локальной копии, и запрашивает её у адаптера. Ваш адаптер пойдёт и извлечёт запись из вашего слоя персистентности; обычно это будет JSON-представление записи, предоставляемое HTTP-сервером.

Diagram showing process for finding an unloaded record

Как показано на диаграмме выше, адаптер не всегда может немедленно вернуть запрошенную запись. В этом случае адаптер должен выполнить асинхронный запрос к серверу, и только после завершения загрузки этого запроса запись может быть создана со своими базовыми данными.

Из-за этой асинхронности хранилище немедленно возвращает обещание из метода findRecord(). Аналогично, все запросы, которые хранилище отправляет адаптеру, также возвращают обещания.

После того, как запрос к серверу возвращает JSON-данные для запрошенной записи, адаптер разрешает обещание, которое он вернул хранилищу, с помощью JSON.

Затем хранилище использует этот JSON, инициализирует запись данными JSON и разрешает обещание, возвращённое вашему приложению, с загруженной записью.

Diagram showing process for finding an unloaded record after the payload has returned from the server

Давайте рассмотрим, что происходит, если вы запрашиваете запись, которая уже есть в кэше хранилища.

Diagram showing process for finding an unloaded record after the payload has returned from the server

В этом случае, поскольку хранилище уже знало о записи, оно возвращает обещание, которое разрешается записью немедленно. Ему не нужно запрашивать у адаптера (и, следовательно, у сервера) копию, так как оно уже сохранило её локально.

Модели, записи, адаптеры и хранилище — основные понятия, которые вы должны понять, чтобы максимально использовать Ember Data. В следующих разделах более подробно рассматриваются каждое из этих понятий и то, как их использовать вместе.

© 2020 Yehuda Katz, Tom Dale and Ember.js contributors
Licensed under the MIT License.
https://guides.emberjs.com/v2.18.0/models

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API