Библиотека idb-keyval предоставляет простой интерфейс
для работы с IndexedDB, а реактивная система Svelte позволяет удобно
управлять состоянием через сторы. Их сочетание даёт устойчивое решение
для хранения данных с персистентностью без усложнения архитектуры.
Обычный стор в Svelte создаётся через writable. Однако
его состояние теряется при перезагрузке страницы. Персистентный стор
расширяет стандартный, добавляя:
Таким образом, состояние синхронизируется между памятью приложения и постоянным хранилищем.
npm install idb-keyval
Импорт необходимых функций:
import { writable } from 'svelte/store';
import { get, set } from 'idb-keyval';
Персистентный стор реализуется как фабричная функция:
export function persistentStore(key, initialValue) {
const store = writable(initialValue);
// загрузка из IndexedDB
get(key).then(value => {
if (value !== undefined) {
store.set(value);
}
});
// подписка на изменения
store.subscribe(value => {
set(key, value);
});
return store;
}
key — уникальный идентификатор в IndexedDBinitialValue используется до загрузки данныхimport { persistentStore } from './stores';
export const userSettings = persistentStore('settings', {
theme: 'light',
language: 'ru'
});
В Svelte-компоненте:
<script>
import { userSettings } from './stores';
</script>
<sel ect bind:value={$userSettings.theme}>
<option value="light">Светлая</option>
<option value="dark">Тёмная</option>
</select>
Изменение автоматически сохраняется в IndexedDB.
Проблема: значение загружается асинхронно, и компонент сначала
получает initialValue.
Решение — добавить флаг загрузки:
export function persistentStore(key, initialValue) {
const { subscribe, set: internalSet } = writable({
data: initialValue,
loaded: false
});
get(key).then(value => {
internalSet({
data: value ?? initialValue,
loaded: true
});
});
return {
subscribe,
set(value) {
internalSet({ data: value, loaded: true });
set(key, value);
}
};
}
Использование:
{#if $store.loaded}
<p>{$store.data}</p>
{:else}
<p>Загрузка...</p>
{/if}
При наличии нескольких вкладок браузера возникает задача синхронизации состояния.
idb-keyval не предоставляет события изменений, поэтому
возможны подходы:
const channel = new BroadcastChannel('store-sync');
store.subscribe(value => {
set(key, value);
channel.postMessage(value);
});
channel.onmess age = (event) => {
store.set(event.data);
};
Менее надёжный, но возможный способ — использование
localStorage как триггера.
Частые изменения могут привести к избыточным операциям записи.
import { debounce } fr om 'lodash-es';
const save = debounce(value => set(key, value), 300);
store.subscribe(value => {
save(value);
});
let lastValue;
store.subscribe(value => {
if (JSON.stringify(value) !== JSON.stringify(lastValue)) {
lastValue = value;
set(key, value);
}
});
При хранении сложных объектов важно учитывать:
Пример:
persistentStore('cart', {
items: [],
total: 0
});
Изменения вложенных свойств требуют замены объекта:
cart.update(state => {
return {
...state,
items: [...state.items, newItem]
};
});
Операции IndexedDB могут завершаться с ошибками:
get(key)
.then(value => {
if (value !== undefined) store.set(value);
})
.catch(err => {
console.error('Ошибка загрузки:', err);
});
store.subscribe(value => {
set(key, value).catch(err => {
console.error('Ошибка сохранения:', err);
});
});
Удаление значения:
import { del } from 'idb-keyval';
del('settings');
Сброс стора:
store.set(initialValue);
Для избежания конфликтов ключей используется соглашение:
persistentStore('app:user');
persistentStore('app:settings');
persistentStore('app:cache');
Либо динамическое формирование:
const createKey = (module, name) => `${module}:${name}`;
idb-keyval позволяет использовать собственный store:
import { Store } from 'idb-keyval';
const customStore = new Store('app-db', 'app-store');
set('key', value, customStore);
get('key', customStore);
Интеграция:
export function persistentStore(key, initialValue, db = customStore) {
const store = writable(initialValue);
get(key, db).then(value => {
if (value !== undefined) store.set(value);
});
store.subscribe(value => {
set(key, value, db);
});
return store;
}
Иногда требуется отложенная инициализация:
let store;
export function getStore() {
if (!store) {
store = persistentStore('lazy', {});
}
return store;
}
Можно строить производные сторы:
import { derived } from 'svelte/store';
export const user = persistentStore('user', null);
export const isLoggedIn = derived(user, $user => !!$user);
Не всегда требуется сохранять весь стор:
store.subscribe(value => {
const { tempData, ...persisted } = value;
set(key, persisted);
});
При изменении структуры данных:
const VERSION = 2;
get(key).then(value => {
if (!value || value.version !== VERSION) {
store.set({ ...initialValue, version: VERSION });
} else {
store.set(value);
}
});
Оптимизации:
Персистентный стор на базе idb-keyval в Svelte
обеспечивает:
Такой подход особенно полезен для: