Запросы и индексы
Запросы фильтруют, сортируют и режут коллекцию. Используйте их вместо .items с методами массивов: они едут по объявленным индексам, обновляются реактивно и держат идентичность результата стабильной - ровно то, что нужно списковым UI.
Дескрипторы полей живут на модели (Todo.done), так что для построения запроса экземпляр в руках не нужен.
Примеры на этой странице выполняются в активном скоупе - см. Коллекции и экземпляры.
Фильтрация
const Todo = staticModel({
data: {
title: f.string(),
done: f.boolean(false).indexed(),
priority: f.number(0).indexed("ord"),
},
});
const todos = collection(Todo);
todos.add([
{ title: "a", priority: 1 },
{ title: "b", priority: 5, done: true },
{ title: "c", priority: 9 },
]);
todos.where(Todo.done.eq(false)).count; // 2
todos.where(Todo.priority.between(2, 9)).ids; // id «b» и «c»
todos.where(Todo.title.startsWith("a")).first; // экземпляр «a»Цепочка вызовов сужает выборку дальше; обычная функция - запасной выход, она просто проходит по всем экземплярам:
todos
.where(Todo.done.eq(false))
.where((t) => t.title.value.length > 1);Сортировка и срезы
todos.sort(Todo.priority.desc).take(2).items; // топ-2 по приоритетуПроекции и массовые операции
const done = todos.where(Todo.done.eq(true));
done.select(Todo.title); // ["b"] - значения поля, не экземпляры
done.set(Todo.done, false); // запись каждому совпавшему
todos.where(Todo.priority.lt(2)).remove(); // dispose каждого совпавшегоОдин экземпляр по id
get(id) - реактивный вид одного экземпляра:
const view = computed(() => todos.get("42"));
view.value; // null - ещё не загружен
todos.add({ id: "42", title: "x" });
view.value; // экземпляр
todos.remove("42");
view.value; // снова null - без throw посреди чтенияОн также разрешает старые id после rebind, так что компонент с id из роута продолжает работать.
Как сделать быстро
Без индексов запросы сканируют. Их объявление меняет план, а не код:
.indexed()-eq-предикаты читают хеш-бакет;.indexed("ord")- диапазоны иsortпо полю используют сортированный вид.
Индексы поддерживаются при записи, поэтому результаты никогда не устаревают; сортированный вид перестраивается лениво при первом запросе после изменения - тысяча записей за кадр стоит одну перестройку.
Реактивность
Терминалы запросов - реактивные чтения. Computed или реакция над ними перезапускаются, когда результат может измениться:
const activeCount = computed(() => todos.where(Todo.done.eq(false)).count);
activeCount.value; // 2
todos.first!.done.value = true;
activeCount.value; // 1 - пересчитаноСтабильные результаты
Планы интернируются по форме; значения - bind-параметры. Цепочка, пересобранная с нуля, возвращает те же массивы, пока результат не изменился:
const a = todos.where(Todo.priority.gte(2)).items;
const b = todos.where(Todo.priority.gte(2)).items;
a === b; // true
todos.first!.title.value = "переименовано"; // этот результат не задет
todos.where(Todo.priority.gte(2)).items === a; // по-прежнему trueПоэтому пересборку запроса на каждом рендере можно не считать: UI-биндинги ререндерятся, только когда множество действительно изменилось.
Контракт
interface Query<T> extends Iterable<T> {
readonly items: T[];
readonly ids: string[];
readonly count: number;
readonly first: T | null;
where(predicate: Predicate | ((item: T) => boolean)): Query<T>;
sort(by: SortToken): Query<T>;
take(count: number): Query<T>;
select(field: Descriptor): unknown[];
set(field: Descriptor, value: unknown): void;
remove(): void; // массовый dispose
toArray(): T[];
}
// операторы дескрипторов
Todo.field.eq(v) .neq(v) .gt(v) .gte(v) .lt(v) .lte(v)
Todo.field.between(from, to) .startsWith(prefix) .includes(part)
Todo.field.asc Todo.field.descДескрипторы привязаны к своей модели - чужой дескриптор даёт ошибку, а не пустой результат.
Частые кейсы
Используйте запросы для:
- списковых экранов - фильтр + сортировка +
take, пересобранные прямо в рендере; - бейджей и счётчиков - computed над
count; - поиска по вторичному ключу - поле
.indexed().unique()плюсeq; - массовых действий -
setиremove()на отфильтрованном запросе.
Связанные разделы
- Поля и данные - объявление
.indexed()-полей. - UI-биндинги - рендер запросов в React и Vue.
- Объединения - запросы по смешанным коллекциям.