Надёжность и синхронизация
Устойчивость и производительность
Debounce и throttle
// Значение записывается только через 500мс после остановки ввода.
const { value: draft } = useStorage('draft', { defaultValue: '', debounce: 500 })
// Пишется не чаще раза в 200мс, пока перетаскивается слайдер, вместо
// ожидания остановки.
const { value: volume } = useStorage('volume', { defaultValue: 50, throttle: 200 })Они взаимоисключающие — если заданы оба, побеждает throttle. В любом случае запись, всё ещё ожидающая при размонтировании компонента, сбрасывается немедленно, а не отбрасывается.
Восстановление при превышении квоты
При QuotaExceededError useStorage сначала зачищает истёкшие по TTL записи этого адаптера (по той же логике, что и TTLManager.cleanExpired()) и повторяет попытку один раз. Если этого недостаточно, включите evictOnQuota, чтобы дополнительно вытеснять наименее недавно записанные другие ключи того же адаптера:
const { value } = useStorage('cache-entry', {
defaultValue: null,
evictOnQuota: { max: 3 }, // вытеснить до 3 других ключей перед тем, как сдаться
})Вытеснение может оценивать возраст только обычных (незашифрованных, несжатых) конвертов — оно оставляет в покое зашифрованные/сжатые/подписанные ключи, принадлежащие другим вызовам useStorage(), поскольку их возраст нельзя безопасно проверить без их собственных ключей.
Сбои записи, не связанные с квотой (полный диск, сломанный адаптер), сообщаются как { type: 'write-failed', key, error } через onError — они никогда не выбрасываются изнутри внутренней реактивной записи, что иначе было бы необработанным отклонением промиса, которое ваше приложение никогда не увидит.
Undo / redo
const {
value: text,
undo,
redo,
canUndo,
canRedo,
} = useStorage('editor-content', {
defaultValue: '',
history: 20, // хранить последние 20 значений
})
text.value = 'draft one'
text.value = 'draft two'
undo() // text.value === 'draft one'
redo() // text.value === 'draft two'История живёт только в памяти — она не сохраняется между перезагрузками и сама по себе не пишется в хранилище. canUndo/canRedo реактивны, так что вы можете отключать соответствующие кнопки в UI.
Синхронизация вкладок
Когда sync: true, записи в value рассылаются всем остальным открытым вкладкам через BroadcastChannel. Удалённые обновления применяются молча (без обратной записи в хранилище). Откатывается на window.addEventListener('storage', ...), если BroadcastChannel недоступен.
const { value: cart } = useStorage('cart', {
defaultValue: [] as CartItem[],
sync: true,
})
// cart.value автоматически остаётся синхронизированным во всех вкладкахSyncOptions
| Опция | Тип | По умолчанию | Описание |
|---|---|---|---|
channel | string | 'vue-storage-kit' | Имя BroadcastChannel |
leader | boolean | false | Включить выборы лидера — только одна вкладка пишет в хранилище при конфликте |
debounce | number | 50 | Миллисекунды для дебаунса исходящих рассылок |
Выборы лидера используют navigator.locks. Вкладка-лидер удерживает именованную блокировку на всё время своей жизни. Когда лидер закрывается, другая вкладка автоматически захватывает блокировку и становится новым лидером. При leader: true конфликты разрешаются по принципу «последняя запись побеждает» по временной метке — при ничьей сохраняется версия лидера.
const { value: sharedState } = useStorage('shared', {
defaultValue: { count: 0 },
sync: { channel: 'app-sync', leader: true, debounce: 100 },
})Использование TabSync напрямую
import { TabSync } from 'vue-storage-kit/sync'
const sync = new TabSync({ channel: 'custom-channel', leader: true })
await sync.start()
sync.subscribe('my-key', (rawValue) => {
console.log('Received from another tab:', rawValue)
})
sync.broadcast('my-key', JSON.stringify({ count: 1 }), Date.now())
sync.stop()