idb-keyval — это легковесная библиотека для работы с
IndexedDB в браузере, упрощающая хранение ключ-значение. Она идеально
подходит для использования совместно с Zustand для сохранения состояния
приложения между сессиями. Библиотека предоставляет простые функции:
set, get, del,
clear, keys, что позволяет легко управлять
данными без необходимости писать сложные запросы IndexedDB вручную.
Пример базового использования:
import { set, get, del, clear } from 'idb-keyval';
// Сохранение значения
await set('theme', 'dark');
// Получение значения
const theme = await get('theme'); // 'dark'
// Удаление значения
await del('theme');
// Очистка всех данных
await clear();
Zustand позволяет создать глобальное состояние приложения. Чтобы сохранять его в IndexedDB, можно использовать middleware или кастомные функции для синхронизации состояния.
Пример базовой интеграции:
import create from 'zustand';
import { persist } from 'zustand/middleware';
import { set, get } from 'idb-keyval';
const useStore = create(
persist(
(set, get) => ({
theme: 'light',
toggleTheme: () => set({ theme: get().theme === 'light' ? 'dark' : 'light' }),
}),
{
name: 'app-state',
getStorage: () => ({
getItem: async (name) => {
const value = await get(name);
return value ? JSON.stringify(value) : null;
},
setItem: async (name, value) => {
await set(name, JSON.parse(value));
},
removeItem: async (name) => {
await del(name);
},
}),
}
)
);
Ключевые моменты:
persist ожидает работу с
localStorage-подобными объектами.getStorage позволяет указать кастомное хранилище, в
данном случае idb-keyval.getItem, setItem и
removeItem асинхронные, что соответствует природе
IndexedDB.Для более сложных состояний, содержащих массивы или объекты, важно правильно обрабатывать асинхронные операции. Например, если необходимо загружать состояние при инициализации приложения:
import { get } from 'idb-keyval';
async function initializeState() {
const savedState = await get('app-state');
return savedState || { theme: 'light', user: null };
}
const useStore = create((set) => ({
theme: 'light',
user: null,
initialize: async () => {
const state = await initializeState();
set(state);
},
toggleTheme: () => set((state) => ({ theme: state.theme === 'light' ? 'dark' : 'light' })),
}));
Особенности подхода:
useStore.subscribe((state) => {
set('app-state', state);
});
idb-keyval поддерживает любые сериализуемые объекты
JavaScript, включая массивы, объекты с вложенностью и даже
Map/Set при дополнительной сериализации.
Рекомендуется всегда использовать JSON при интеграции с Zustand, чтобы
избежать проблем с восстановлением состояния:
const complexState = {
user: { id: 1, name: 'Alice' },
preferences: { theme: 'dark', notifications: true },
history: [1, 2, 3],
};
// Сохранение
await set('complex-state', JSON.stringify(complexState));
// Загрузка
const savedState = JSON.parse(await get('complex-state'));
Для поддержания актуального состояния удобно использовать подписки Zustand. Каждое изменение состояния можно автоматически сохранять в IndexedDB, минимизируя риск потери данных:
const unsubscribe = useStore.subscribe(
(state) => {
set('app-state', state);
}
);
// Отмена подписки при необходимости
unsubscribe();
Преимущества такого подхода:
set.Для разных частей приложения полезно использовать отдельные ключи в IndexedDB, чтобы избежать конфликтов:
await set('user-settings', { theme: 'dark', language: 'ru' });
await set('session-data', { token: 'abcd1234', expires: 1680000000 });
Можно создать утилиты для генерации ключей:
const makeKey = (namespace, key) => `${namespace}:${key}`;
await set(makeKey('user', 'theme'), 'dark');
await set(makeKey('user', 'language'), 'ru');
Это обеспечивает модульность и простоту управления различными частями состояния.
await при всех операциях с
idb-keyval, чтобы избежать состояния гонки.try/catch, так как
IndexedDB может быть недоступен в некоторых браузерах или режимах
приватного просмотра.Эти методы позволяют реализовать полноценное персистентное состояние
Zustand через idb-keyval с минимальными усилиями,
обеспечивая стабильность и масштабируемость приложения.