Виды моделей
model и staticModel одинаково объявляют поля, трейты и связи, живут в одних и тех же коллекциях и дают одну поверхность экземпляра. Разница ровно одна: когда создаются юниты из setup. Эта страница - про эту разницу и про то, когда не подходит ни тот ни другой, а нужен обычный owner из ядра.
staticModel: юниты один раз
Статическая модель строит свой реактивный граф единожды. Экземпляр - лёгкий скоуп над общими юнитами: создание записывает значения полей и больше ничего:
const Todo = staticModel({
data: { done: f.boolean(false) },
setup(self) {
const toggled = event<void>();
reaction({
on: toggled,
run: () => {
self.done.value = !self.done.value;
},
});
return { toggled };
},
});setup выполняется один раз, при объявлении. Каждая реакция всё равно ведёт себя по-экземплярно - await a.toggled() переключает только a, - а API экземпляра на self (self.id, self.json()) доступен в телах юнитов, где конкретный экземпляр существует. Скоупы уничтоженных экземпляров переиспользуются пулом.
Внешним ресурсам на каждый экземпляр тоже есть место: self.onCleanup из тела юнита привязывает отписку к амбиентному экземпляру - без стора и без отдельных юнитов на экземпляр.
Это дефолтный вид: тысяча строк списка, элементов ленты или ячеек таблицы за кадр - штатная нагрузка.
model: юниты на каждый экземпляр
Динамическая модель выполняет setup для каждого экземпляра, и setup получает вход создания вторым аргументом:
const ReportScreen = model({
data: {
period: f.string("month"),
rows: f.array(f.number(), []).local(),
},
setup(self, props) {
// это замыкание принадлежит ОДНОМУ экземпляру - создавайте что нужно
const refresh = event<void>();
const total = computed(() => self.rows.value.reduce((a, b) => a + b, 0));
reaction({
on: refresh,
run: async () => {
self.rows.value = await api.report(self.period.value, props.filters);
},
});
return { refresh, total };
},
});
const report = collection(ReportScreen).add({ period: "week", filters: { team: 1 } });
await report.refresh();
report.total.value;Что происходит:
- сторы, события, computed'ы и реакции создаются для этого экземпляра и принадлежат ему -
dispose()разбирает их все; props- сырой входadd: ключи, не являющиеся полями (какfiltersвыше), видныsetup, хотя нигде не хранятся;- раз у каждого экземпляра настоящие юниты,
setupможет ветвиться: создавать разные реакции разным экземплярам, держать в замыканиях их собственные ресурсы.
Цена - настоящие аллокации на каждый экземпляр. Берите model для немногих индивидуальных сущностей: экранов, редакторов, визардов, долгоживущих рабочих областей.
Как выбрать
staticModel | model | |
|---|---|---|
| Юниты | один общий граф | создаются в setup на экземпляр |
| Цена экземпляра | скоуп и карта значений | сторы + реакции + владелец |
setup выполняется | один раз, при объявлении | на каждый add |
Аргументы setup | (self) | (self, props) - вход создания |
| Пер-инстансные замыкания | нет - состояние в полях | да |
| Пулинг | скоупы переиспользуются | нет - уничтожение настоящее |
| Типичное количество | сотни и тысячи | единицы |
Всё остальное - поля, трейты, связи, запросы, сериализация, keep в биндингах - совпадает. Начинайте со staticModel; переходите на model, когда экземпляру действительно нужны собственные динамически создаваемые юниты.
А обычный owner?
owner из ядра - примитив уровнем ниже: одноразовое дерево юнитов и ресурсов. Экземпляр динамической модели внутри и есть owner - model добавляет поверх слой сущности:
- декларацию: поля, которые валидируют вход и сериализуются обратно;
- дом: коллекцию с get-or-create, поиском по id и мержем;
- связи с политиками удаления, запросы с индексами,
rebindи алиасы.
Правило выбора:
- доменная сущность - у неё данные, id и место в списках - это
modelилиstaticModel; - подсистема приложения - менеджер сокетов, плеер, кеш, фоновый процесс - это
owner: у неё ресурсы и время жизни, но нет сетевой формы, id и того, по чему искать; - если owner'у понадобились реестр id и сериализатор - он хотел быть моделью.
Контракт
model({
with?, data?, name?,
setup?(self, props): members | void, // на экземпляр; props - вход add
});
staticModel({
with?, data?, name?,
setup?(self): members | void, // один раз; API экземпляра - в телах юнитов
});Оба возвращают определение, взаимозаменяемо работающее с collection, трейтами, связями, объединениями и биндингами.
Частые кейсы
- Строки списков, элементы лент, ячейки таблиц -
staticModel. - Экраны и редакторы со своей обвязкой у каждого экземпляра -
model, часто сkeep. - Доменные сущности, нужные обоим мирам, - любой вид: поверхность одна, переключение потом - замена одного слова.
- Не-сущностные подсистемы -
ownerиз ядра, вообще не модель.
Связанные разделы
- Коллекции и экземпляры - общая поверхность экземпляра.
- Владельцы и очистка - примитив уровнем ниже.
- UI-биндинги - экранные модели и
keep.