В большинстве современных фронтенд-фреймворков работа с данными — это всегда «сборка» из нескольких библиотек. Вы выбираете отдельный инструмент для управления состоянием (Redux, Pinia), отдельный HTTP-клиент (Axios, Fetch), отдельную библиотеку для кэширования и синхронизации. Каждый инструмент нужно настроить, интегрировать с остальными и поддерживать.
Ext JS решает эту задачу иначе. В состав фреймворка входит Data Package — встроенный слой данных, который полностью отвечает за загрузку, хранение, манипуляцию и сохранение данных в приложении. Он отделяет UI-компоненты от источника данных и работает с любым бэкендом — REST, GraphQL, WebSocket, LocalStorage — без изменения кода интерфейса.
В этой статье мы разберем три ключевых класса Data Package — Model, Store и Proxy — и покажем, почему этот слой считается «сердцем бизнес-логики» Ext JS-приложения.
🧱 Три кита Data Package
Data Package состоит из множества классов, но три из них используются практически в каждом приложении:
-
Model (
Ext.data.Model) — представляет сущность приложения. -
Store (
Ext.data.Store) — коллекция экземпляров моделей с клиентским кэшем. -
Proxy (
Ext.data.proxy.Proxy) — «шлюз» для загрузки и сохранения данных на сервере.
Эти классы работают вместе как единый механизм: Proxy загружает данные, Store их хранит и управляет ими, а Model описывает структуру каждой записи. UI-компоненты (Grid, Form, Chart) подключаются к Store через механизм привязки данных (data binding) и автоматически обновляются при любых изменениях.
📦 Model: описание сущности и бизнес-правил
Model — это центральный элемент Data Package. Она представляет сущность приложения: Пользователя, Заказ, Товар, Сделку. На простейшем уровне Model определяет набор полей и связанную с ними бизнес-логику.
Создание Model
Обычно модели определяются через наследование от общей базовой модели. Это позволяет задать общие настройки (например, proxy или схему) в одном месте:
Ext.define('MyApp.model.Base', { extend: 'Ext.data.Model', fields: [{ name: 'id', type: 'int' }], schema: { namespace: 'MyApp.model', proxy: { type: 'ajax', url: '{entityName}.json', reader: { type: 'json', rootProperty: '{entityName:lowercase}' } } } });
Здесь схема (schema) определяет общие настройки для всех моделей: пространство имён и шаблон Proxy по умолчанию. Это избавляет от необходимости дублировать одни и те же настройки в каждой модели.
Поля и валидация
Каждая Model может содержать поля различных типов (string, int, float, boolean, date) и правила валидации. В Ext JS есть пять встроенных валидаторов:
-
Presence — проверяет, что поле заполнено.
-
Length — проверяет длину строки (min/max).
-
Format — проверяет соответствие регулярному выражению.
-
Inclusion — проверяет, что значение входит в заданный список.
-
Exclusion — проверяет, что значение не входит в заданный список.
Пример модели с валидацией:
Ext.define('MyApp.model.User', { extend: 'MyApp.model.Base', fields: [ { name: 'name', type: 'string' }, { name: 'email', type: 'string' } ], validators: { name: [ 'presence', { type: 'length', min: 3 }, { type: 'exclusion', list: ['admin', 'root'] } ], email: [ { type: 'format', matcher: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ } ] } });
Когда вы создаёте экземпляр модели, вы можете проверить его валидность:
var user = new MyApp.model.User({ name: 'Bender', email: 'bender@planetexpress.com' }); console.log(user.isValid()); // false — имя слишком короткое var errors = user.getValidation(); // возвращает объект с ошибками
🗄️ Store: клиентский кэш и управление данными
Store — это коллекция экземпляров Model. Он содержит локальный кэш данных на клиенте и управляет их загрузкой и сохранением через Proxy.
Создание Store и загрузка данных
var store = new Ext.data.Store({ model: 'MyApp.model.User' }); store.load({ callback: function() { var firstName = this.first().get('name'); console.log(firstName); } });
Store также может загружать данные напрямую, без обращения к серверу:
new Ext.data.Store({ model: 'MyApp.model.User', data: [ { id: 1, name: 'Philip J. Fry' }, { id: 2, name: 'Turanga Leela' } ] });
Сортировка, фильтрация и группировка
Одна из ключевых возможностей Store — локальная сортировка, фильтрация и группировка. Это означает, что данные можно обрабатывать на клиенте без повторных запросов к серверу:
new Ext.data.Store({ model: 'MyApp.model.User', sorters: ['name', 'id'], filters: { property: 'name', value: 'Philip J. Fry' } });
В этом примере данные будут отсортированы сначала по имени, затем по id, и отфильтрованы так, чтобы остались только пользователи с именем «Philip J. Fry».
Chained Stores
Ext JS поддерживает Chained Stores — механизм, который позволяет нескольким компонентам использовать один и тот же набор данных, но с разными настройками сортировки, фильтрации и группировки. Например, Grid может показывать все заказы, а Chart — только заказы за последний месяц, при этом оба компонента используют один и тот же Store.
🔌 Proxy: шлюз к данным, не зависящий от бэкенда
Proxy — это класс, который знает, как загружать и сохранять данные. Он используется Store и Model для взаимодействия с сервером или локальным хранилищем.
Два типа Proxy
-
Client Proxy — работает с данными на клиенте:
-
Memory — хранит данные в памяти.
-
LocalStorage — использует HTML5 LocalStorage.
-
-
Server Proxy — взаимодействует с удалённым сервером:
-
Ajax — обычные AJAX-запросы.
-
REST — RESTful-запросы.
-
JSONP — запросы с обходом CORS.
-
Пример конфигурации Proxy
Ext.define('MyApp.model.User', { extend: 'Ext.data.Model', fields: ['id', 'name', 'email'], proxy: { type: 'rest', url: '/api/users', reader: { type: 'json', rootProperty: 'data' }, writer: { type: 'json' } } });
Ключевое преимущество: Proxy не зависит от протокола и от бэкенда. Вы можете переключиться с REST на GraphQL или с PHP на Node.js, не меняя ни строчки в UI-компонентах. Компоненты просто получают данные из Store — им всё равно, откуда они пришли.
🔗 Associations: связи между моделями
Большинство приложений работают не с изолированными сущностями, а со связанными данными. Например, у Пользователя может быть много Постов, а у Поста — один автор. Ext JS поддерживает несколько типов связей:
-
ManyToOne — много Постов принадлежат одному Пользователю.
-
OneToOne — один Профиль соответствует одному Пользователю.
-
ManyToMany — множество Пользователей могут состоять во множестве Групп.
Пример определения связи ManyToOne
Ext.define('MyApp.model.User', { extend: 'MyApp.model.Base', fields: [{ name: 'name', type: 'string' }] }); Ext.define('MyApp.model.Post', { extend: 'MyApp.model.Base', fields: [ { name: 'userId', reference: 'User', type: 'int' }, { name: 'title', type: 'string' } ] });
После определения связи Ext JS автоматически генерирует методы для работы со связанными данными:
// Загрузка пользователя и всех его постов MyApp.model.User.load(1, { callback: function(user) { console.log('User: ' + user.get('name')); user.posts(function(posts) { posts.each(function(post) { console.log('Post: ' + post.get('title')); }); }); } }); // Создание нового поста для пользователя user.posts().add({ title: 'Новый пост' }); user.posts().sync(); // сохранение на сервере
Загрузка вложенных данных
Когда связи определены, Ext JS может загружать связанные записи в одном запросе. Если сервер вернёт данные в таком формате:
{ "success": true, "user": [{ "id": 1, "name": "Philip J. Fry", "posts": [ { "title": "Post 1" }, { "title": "Post 2" } ] }] }
…то фреймворк автоматически распарсит вложенные данные и создаст связанные записи без дополнительных запросов к серверу.
🆕 Что нового в Ext JS 8.0
В версии 8.0 Data Package получил несколько важных улучшений:
-
Расширенная поддержка ECMAScript до ES2025 — теперь вы можете использовать современный синтаксис (
let,const, стрелочные функции) в моделях и Store без транспиляции. -
Улучшенная производительность Grid при работе с большими данными — горизонтальная буферизация и виртуализация колонок позволяют плавно отображать таблицы с более чем 1000 колонок.
-
Улучшенная поддержка ARIA — полная поддержка полей форм для скринридеров (JAWS, Narrator, TalkBack, VoiceOver).
💎 Заключение
Data Package — это не просто «ещё один способ загрузить данные». Это архитектурный фундамент Ext JS-приложения, который:
| Компонент | Что решает |
|---|---|
| Model | Описание сущностей, полей, валидаций и связей |
| Store | Клиентский кэш, сортировка, фильтрация, группировка, Chained Stores |
| Proxy | Загрузка и сохранение данных, независимость от бэкенда |
| Associations | Связи между моделями (ManyToOne, OneToOne, ManyToMany) |
| Schema | Централизованное управление всеми моделями приложения |
В большинстве фреймворков вам нужно самостоятельно собирать этот слой из десятка библиотек. В Ext JS он встроен, интегрирован и работает согласованно. Именно поэтому Data Package считается «сердцем бизнес-логики» Ext JS-приложения.
Если вы строите корпоративное приложение, где важна надёжная работа с данными, — Data Package заслуживает самого пристального внимания.
Если у вас есть опыт работы с Data Package в Ext JS или вопросы по моделям, Store и Proxy, поделитесь в комментариях.