Skip to content

Надёжность и синхронизация

Устойчивость и производительность

Debounce и throttle

ts
// Значение записывается только через 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, чтобы дополнительно вытеснять наименее недавно записанные другие ключи того же адаптера:

ts
const { value } = useStorage('cache-entry', {
  defaultValue: null,
  evictOnQuota: { max: 3 }, // вытеснить до 3 других ключей перед тем, как сдаться
})

Вытеснение может оценивать возраст только обычных (незашифрованных, несжатых) конвертов — оно оставляет в покое зашифрованные/сжатые/подписанные ключи, принадлежащие другим вызовам useStorage(), поскольку их возраст нельзя безопасно проверить без их собственных ключей.

Сбои записи, не связанные с квотой (полный диск, сломанный адаптер), сообщаются как { type: 'write-failed', key, error } через onError — они никогда не выбрасываются изнутри внутренней реактивной записи, что иначе было бы необработанным отклонением промиса, которое ваше приложение никогда не увидит.

Undo / redo

ts
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 недоступен.

ts
const { value: cart } = useStorage('cart', {
  defaultValue: [] as CartItem[],
  sync: true,
})
// cart.value автоматически остаётся синхронизированным во всех вкладках

SyncOptions

ОпцияТипПо умолчаниюОписание
channelstring'vue-storage-kit'Имя BroadcastChannel
leaderbooleanfalseВключить выборы лидера — только одна вкладка пишет в хранилище при конфликте
debouncenumber50Миллисекунды для дебаунса исходящих рассылок

Выборы лидера используют navigator.locks. Вкладка-лидер удерживает именованную блокировку на всё время своей жизни. Когда лидер закрывается, другая вкладка автоматически захватывает блокировку и становится новым лидером. При leader: true конфликты разрешаются по принципу «последняя запись побеждает» по временной метке — при ничьей сохраняется версия лидера.

ts
const { value: sharedState } = useStorage('shared', {
  defaultValue: { count: 0 },
  sync: { channel: 'app-sync', leader: true, debounce: 100 },
})

Использование TabSync напрямую

ts
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()