Надёжность и синхронизация
Устойчивость и производительность
Debounce и throttle
// Значение записывается только через 500мс после остановки ввода.
const { value: draft } = useStorage('draft', { defaultValue: '', debounce: 500 })
// Пишется не чаще раза в 200мс, пока перетаскивается слайдер, вместо
// ожидания остановки.
const { value: volume } = useStorage('volume', { defaultValue: 50, throttle: 200 })Они взаимоисключающие — если заданы оба, побеждает throttle, и при создании один раз выводится предупреждающий console.warn в dev-режиме. В любом случае запись, всё ещё ожидающая при размонтировании компонента, сбрасывается немедленно, а не отбрасывается.
Восстановление при превышении квоты
При 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()useBroadcastChannel
Низкоуровневая, обобщённая примитива pub/sub поверх BroadcastChannel — вообще не привязана к useStorage(), в отличие от опции sync выше. Используйте её для обмена сообщениями между вкладками, не связанного с сохранением ключа хранилища (живой чат между вкладками, тост «кто-то другой внёс изменения», координация общего таймера).
function useBroadcastChannel<T>(channelName: string): UseBroadcastChannelReturn<T>isSupported
boolean
Существует ли BroadcastChannel в этом окружении. При false post() становится тихим no-op, и сообщения никогда не доходят.
lastMessage
ShallowRef<T | null>
Последнее полученное сообщение.
messages
Ref<T[]>
Все полученные до сих пор сообщения, от старых к новым.
post
(message: T) => void
Отправляет сообщение всем остальным вкладкам/окнам, подписанным на тот же channelName.
close
() => void
Закрывает базовый канал. Запускается автоматически при уничтожении scope, если вызван внутри активного effect scope.
import { useBroadcastChannel } from 'vue-storage-kit'
interface ChatMessage {
user: string
text: string
}
const { lastMessage, post } = useBroadcastChannel<ChatMessage>('team-chat')
post({ user: 'ada', text: 'Hello from this tab' })
// lastMessage.value обновляется на каждой другой вкладке, подписанной на 'team-chat'