Skip to content

Виды моделей

model и staticModel одинаково объявляют поля, трейты и связи, живут в одних и тех же коллекциях и дают одну поверхность экземпляра. Разница ровно одна: когда создаются юниты из setup. Эта страница - про эту разницу и про то, когда не подходит ни тот ни другой, а нужен обычный owner из ядра.

staticModel: юниты один раз

Статическая модель строит свой реактивный граф единожды. Экземпляр - лёгкий скоуп над общими юнитами: создание записывает значения полей и больше ничего:

ts
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 получает вход создания вторым аргументом:

ts
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 для немногих индивидуальных сущностей: экранов, редакторов, визардов, долгоживущих рабочих областей.

Как выбрать

staticModelmodel
Юнитыодин общий графсоздаются в 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 и сериализатор - он хотел быть моделью.

Контракт

ts
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 из ядра, вообще не модель.

Связанные разделы