Подписка на изменения в базе данных в Dexie.js строится вокруг нескольких механизмов: хуков таблиц, наблюдаемых запросов и событийных API. Эти инструменты позволяют реагировать на изменения данных без ручного опроса и пересборки состояния приложения.
В основе лежит идея отслеживания операций CRUD на уровне таблиц и последующей трансляции этих событий в слой приложения.
Dexie.js предоставляет механизм table hooks, позволяющий перехватывать операции до и после их выполнения. Эти хуки работают синхронно с транзакциями и являются фундаментом для построения реактивности.
Каждая таблица поддерживает три базовые точки расширения:
creating — перед добавлением записиupdating — перед обновлением записиdeleting — перед удалением записиreading — при чтении записи из IndexedDBХук creating вызывается до фактической вставки объекта в
хранилище. Он позволяет модифицировать данные или вычислить поля.
db.users.hook('creating', (primKey, obj, transaction) => {
obj.createdAt = Date.now();
});
Особенности поведения:
Хук updating активируется при частичном обновлении
записи через update() или modify().
db.users.hook('updating', (mods, primKey, obj, transaction) => {
mods.updatedAt = Date.now();
});
Характеристики:
mods содержит только изменяемые поляmods влияет на итоговую записьХук удаления позволяет реагировать на удаление записей до фактического выполнения операции.
db.users.hook('deleting', (primKey, obj, transaction) => {
db.audit.add({
type: 'delete',
id: primKey,
snapshot: obj
});
});
Использование:
Хук чтения применяется при извлечении данных из IndexedDB.
db.users.hook('reading', (obj) => {
obj.fullName = obj.firstName + ' ' + obj.lastName;
});
Особенности:
Dexie выполняет хуки в строго определённой последовательности:
creating / updating /
deleting (в зависимости от операции)reading при извлечении данныхНесколько хуков одного типа выполняются последовательно в порядке регистрации.
db.users.hook('creating', fn1);
db.users.hook('creating', fn2);
Здесь fn1 выполнится раньше fn2.
Удаление хуков возможно через unsubscribe:
const hook = db.users.hook('creating').subscribe(callback);
hook.unsubscribe();
Для более высокого уровня реактивности используется
liveQuery, который отслеживает зависимости запроса и
пересчитывает результат при изменении данных.
import { liveQuery } fr om "dexie";
const activeUsers$ = liveQuery(() =>
db.users.wh ere('active').equals(1).toArray()
);
Механизм работы:
Результат liveQuery представляет собой поток значений,
на который можно подписаться.
const subscription = activeUsers$.subscribe({
next: (users) => {
console.log(users);
},
error: (err) => {
console.error(err);
}
});
Поведение:
Подписка на реактивные источники требует явного управления временем жизни.
subscription.unsubscribe();
Сценарии применения:
Dexie анализирует выполнение запроса внутри liveQuery и
определяет зависимости:
При изменении любой из зависимостей происходит инвалидация кэша и повторный запуск функции.
liveQuery(() =>
db.orders.where('status').anyOf(['new', 'pending']).toArray()
);
Любое изменение orders с соответствующим
status вызывает пересчёт.
Хуки и liveQuery дополняют друг друга, но работают на
разных уровнях:
Пример комбинированного использования:
db.orders.hook('creating', (id, obj) => {
obj.createdAt = Date.now();
});
const orders$ = liveQuery(() =>
db.orders.orderBy('createdAt').toArray()
);
Хук формирует данные, реактивный запрос отслеживает их изменения.
Dexie позволяет строить цепочки вычисляемых данных поверх
liveQuery.
const totalPrice$ = liveQuery(() =>
db.orders.toArray().then(orders =>
orders.reduce((sum, o) => sum + o.price, 0)
)
);
Любое изменение в orders приводит к пересчёту
агрегата.
Такая модель используется для:
Реактивные механизмы Dexie оптимизированы под минимизацию лишних пересчётов.
Ключевые принципы:
При этом сложные вычисления внутри liveQuery могут стать
узким местом, если отсутствует индексация или используются полные
сканирования таблиц.
Ошибки могут возникать на любом этапе выполнения запроса:
liveQuery(() => db.users.toArray())
.subscribe({
error: (err) => {
console.error(err);
}
});
При ошибке поток не завершается автоматически, но дальнейшие изменения могут снова активировать пересчёт при устранении причины.
Результаты liveQuery часто интегрируются с внешними
реактивными библиотеками через адаптеры:
Пример с внешним подписчиком:
const stream = liveQuery(() => db.items.toArray());
stream.subscribe(data => {
store.setState({ items: data });
});
Такая схема позволяет использовать Dexie как источник реактивного состояния приложения без прямой привязки к UI-фреймворку.