В Dexie.js механизм хуков чтения относится к внутреннему этапу
преобразования данных в момент их извлечения из IndexedDB и передачи в
пользовательский код. Этот тип хука позволяет перехватывать каждый
объект, который возвращается из таблицы, и модифицировать его до того,
как он попадёт в результат get, toArray,
each, where().toArray() и других методов
чтения.
Хуки чтения работают на уровне отдельных записей, а не коллекций, и выполняются синхронно в момент десериализации объекта из внутреннего представления IndexedDB.
Dexie предоставляет доступ к хукам через
db.table.hook('reading', ...). Общая форма регистрации
выглядит следующим образом:
db.users.hook('reading', (obj) => {
return obj;
});
Каждый объект, возвращённый из таблицы users, проходит
через этот обработчик.
Хук чтения получает один аргумент — объект записи:
(obj) => modifiedObj
Особенности поведения:
вызывается для каждой записи отдельно;
должен работать синхронно;
может возвращать:
не предназначен для асинхронных операций;
применяется ко всем операциям чтения таблицы.
Если хук возвращает undefined, это не означает
фильтрацию записи — объект всё равно попадёт в результат. Для фильтрации
используются методы коллекций (filter, where),
а не reading hook.
Наиболее распространённый сценарий — нормализация или денормализация данных при чтении.
db.users.hook('reading', (user) => {
user.fullName = `${user.firstName} ${user.lastName}`;
return user;
});
Теперь каждое прочитанное значение из таблицы users
будет содержать поле fullName, хотя оно не хранится в
базе.
Хук чтения часто используется для адаптации структуры данных под бизнес-логику:
db.orders.hook('reading', (order) => {
return {
...order,
totalPrice: order.items.reduce((sum, i) => sum + i.price * i.qty, 0)
};
});
Такой подход позволяет хранить только сырые данные, а производные вычисления выполнять при чтении.
Хук чтения удобен для постепенной миграции схемы без изменения существующих данных.
db.users.hook('reading', (user) => {
if (!user.fullName && user.firstName && user.lastName) {
user.fullName = `${user.firstName} ${user.lastName}`;
}
return user;
});
Это позволяет поддерживать совместимость между версиями приложения без миграции всей базы.
Часто reading hook используется для обратного преобразования данных, например, дешифрации:
db.secrets.hook('reading', (item) => {
item.value = decrypt(item.value);
return item;
});
Важно учитывать, что дешифрация выполняется синхронно, поэтому алгоритмы должны быть быстрыми.
Reading hook не поддерживает асинхронные операции:
// НЕЛЬЗЯ
db.users.hook('reading', async (user) => {
user.extra = await fetchSomething();
return user;
});
Dexie не будет ожидать Promise, и поведение станет непредсказуемым.
Попытка исключать записи через return null или
undefined не работает как фильтр:
db.users.hook('reading', (user) => {
if (user.deleted) return undefined; // не фильтрует корректно
});
Фильтрация должна выполняться через:
db.users.where('deleted').equals(0)
или:
db.users.filter(u => !u.deleted)
Так как hook вызывается для каждой записи:
Оптимальный подход — минимальные и дешёвые операции трансформации.
Если зарегистрировано несколько reading hooks на одной таблице, они выполняются последовательно в порядке добавления:
db.users.hook('reading', (u) => {
u.step1 = true;
return u;
});
db.users.hook('reading', (u) => {
u.step2 = true;
return u;
});
В результате объект последовательно проходит все трансформации.
Reading hook может использоваться для приведения объекта к экземпляру класса:
class User {
constructor(data) {
Object.assign(this, data);
}
get isActive() {
return this.status === 'active';
}
}
db.users.hook('reading', (obj) => {
return Object.setPrototypeOf(obj, User.prototype);
});
Такой подход позволяет добавлять методы и геттеры к объектам, извлекаемым из IndexedDB.
Dexie может возвращать объекты из внутреннего кэша. Reading hook применяется и в этом случае, что приводит к важному эффекту:
Поэтому безопаснее возвращать новый объект:
db.users.hook('reading', (u) => {
return { ...u, hydrated: true };
});
Reading hook срабатывает не только при get, но и при
всех операциях, возвращающих записи:
toArray()each()first()last()where().equals().toArray()При этом hook вызывается после извлечения данных, но до передачи их в пользовательский код коллекции.
db.posts.hook('reading', (post) => {
post.createdAt = new Date(post.createdAt);
return post;
});
db.messages.hook('reading', (msg) => {
msg.isMine = msg.userId === currentUserId;
return msg;
});
db.settings.hook('reading', (s) => {
if (s.themeColor && !s.theme) {
s.theme = s.themeColor;
}
return s;
});
Хук чтения фактически формирует слой “адаптера данных” между хранилищем IndexedDB и доменной моделью приложения. Он позволяет:
При этом его использование требует строгого контроля сложности, так как он становится глобальной точкой трансформации всех читаемых данных из конкретной таблицы.