UI-биндинги
Запросы, экземпляры и определения моделей подключаются к существующим useModel и component из @virentia/react и @virentia/vue - новых хуков учить не нужно. Используйте биндинги, чтобы рендерить коллекции напрямую: компонент подписывается ровно на то, что показывает.
Запрос в компоненте
Список подписывается на запрос; каждая строка - на свой экземпляр:
function TodoRow({ todo }: { todo: InstanceOf<typeof Todo> }) {
const t = useModel(todo);
if (!t) return null;
return <li>{t.title.value}</li>;
}
function ActiveTodos() {
const active = useModel(
todos.where(Todo.done.eq(false)).sort(Todo.priority.desc).take(50),
);
return (
<ul>
{active.items.map((t) => (
<TodoRow key={t.key} todo={t} />
))}
</ul>
);
}Что происходит:
- пересборка цепочки на каждый рендер бесплатна - планы интернируются, результаты мемоизированы;
- список ререндерится только при изменении множества; строка - только на записи своего экземпляра: переименование одной задачи трогает одну строку, а не список;
t.key- стабильный ключ списка: без ремаунтов наrebindи склеенных строк при переиспользовании.
Во Vue те же вызовы возвращают refs: active.value.items, t.value.title.value.
Экземпляр по id
function TodoScreen({ id }: { id: string }) {
const todo = useModel(todos.get(id));
if (!todo) return <NotFound />;
return <h1>{todo.title.value}</h1>;
}get(id) следит за записями своего экземпляра, разрешает старые id после rebind - роут со временным id продолжает работать - и отдаёт null, когда сущности нет или она уничтожена. Никаких исключений посреди рендера.
Экранные модели
Определение модели - фабрика props → экземпляр через её коллекцию, поэтому существующие сигнатуры экранных моделей принимают его напрямую:
const OrderView = component({ model: OrderScreen, view: OrderLayout });
// или императивно:
const order = useModel(OrderScreen, { orderId });Что происходит:
- экземпляр создаётся в предоставленном скоупе - обычный
add; - изменения пропсов мержатся в экземпляр (присутствующие ключи побеждают);
- анмаунт уничтожает экземпляр;
component.create()возвращает controlled-экземпляр, который вью никогда не уничтожает - им владеет создатель.
Состояние сквозь анмаунты: keep
useModel(SettingsScreen, props, { keep: true });
// component({ model: SettingsScreen, view, keep: true })keep меняет одно: анмаунт больше не диспозит. Ремаунт снова находит экземпляр и домерживает текущие пропсы; жизнь завершает явный remove или скоуп.
Кого находит ремаунт:
- без
idв пропсах - единственный экземпляр модели в скоупе (настройки, единственная форма). Несколько живых экземпляров делают выбор неоднозначным - это dev-ошибка, а не молчаливый выбор; - с
idв пропсах - экземпляр с этим id (вкладки, master-detail, id приходит из роута или документа).
Опций cache/key у моделей нет - коллекция уже и есть кеш, keep просто оставляет экземпляр в ней.
Инспектор
Коллекции показываются в инспекторе агрегированно - количество сущностей, размеры индексов, планы запросов - вместо записи на каждый экземпляр. Тысяча строк списка - это нагрузка, а не тысяча скоупов в devtools.
Контракт
// React и Vue, одинаковые формы; Vue возвращает refs
useModel(query): query; // живой вид запроса
useModel(collection.get(id)): Instance | null; // вид сущности
useModel(Definition, props?, { keep? }): Instance | null;
component({ model: Definition, view, keep?, mapProps? });
Component.create(props): Instance; // controlled-экземплярЧастые кейсы
Используйте биндинги для:
- списковых экранов - запрос, собранный в рендере, строки с ключом
t.key; - детальных экранов -
useModel(todos.get(id))с фолбэком наnull; - состояния экрана, переживающего переключение вкладок, -
keep: true; - диалогов и редакторов, которыми владеет родитель, -
component.create().
Связанные разделы
- Запросы и индексы - почему инлайн-цепочки бесплатны.
- Загрузка и сериализация -
rebind, форвардинг, стабильные ключи. - Модели в React и во Vue - фреймворк-специфика.