Nano Stores Persistent
一个用于 Nano Stores 状态管理器的智能存储,用于在 localStorage 中保存数据
并在浏览器标签页之间同步更改。
- 小巧。 仅 296 字节(压缩并经过 brotli 处理)。 零依赖。它使用 Size Limit 来控制体积。
- 具有良好的 TypeScript 支持。
- 框架无关。它支持 SSR。
localStorage可以切换到其他存储。
import { persistentAtom } from '@nanostores/persistent'
export const $locale = persistentAtom('locale', 'en')
Nano Stores Persistent 由 Evil Martians 开发,这是一家专注于 开发者工具、AI 和网络安全初创企业 的美国设计与工程咨询公司。
安装
npm install nanostores @nanostores/persistent
用法
请参阅 Nano Stores 文档 了解如何在 UI 框架中使用 store 并订阅 store 的变更。
原始值 Store
包含原始值的 store 会将所有数据保存在单个 localStorage
键中。
import { persistentAtom } from '@nanostores/persistent'
export const $shoppingCart = persistentAtom<Product[]>('cart', [], {
encode: JSON.stringify,
decode: JSON.parse
})
该存储将在 cart 键中保留其值localStorage。
当 localStorage 中缺少键时,空数组 [] 将是初始值。
您可以通过 set 方法更改存储值。
$shoppingCart.set([...$shoppingCart.get(), newProduct])
你也可以将对象存储在原始存储中。但持久化 Map 存储更好,因为当你向初始值添加新键时,Map 存储会更新值。
有一个简单的包装器用于存储布尔值:
import { persistentBoolean } from '@nanostores/persistent'
export const $reduceMotion = persistentBoolean('reduce-motion')
JSON Store
persistentJSON 是 persistentAtom 的一个便捷快捷方式,它会为您处理 JSON 序列化。
它支持任何有效的 JSON 类型:对象、数组、原始值(string、number、boolean、null)。
import { persistentJSON } from '@nanostores/persistent'
const $cart = persistentJSON<Product[]>('cart', [])
const $theme = persistentJSON<'dark' | 'light' | 'auto'>('theme', 'auto')
$cart.set([...$cart.get(), newProduct])
$theme.set('dark')
您可以省略初始值参数。在这种情况下,我们将添加 null 到存储类型。
const $comments = persistentJSON<Comment[]>('comments')
console.log($comments.get()?.map(i => i.userId)) // Safe access
Map Store
存在一个特殊的键值映射存储。它会将每个键
存储在独立的 localStorage 键中。
import { persistentMap } from '@nanostores/persistent'
export type SettingsValue = {
sidebar: 'show' | 'hide'
theme: 'dark' | 'light' | 'auto'
}
export const $settings = persistentMap<SettingsValue>('settings:', {
sidebar: 'show',
theme: 'auto'
})
该存储将在 settings:sidebar 和 settings:theme 键中保存值。
您可以通过 setKey 方法更改键:
$settings.setKey('sidebar', 'hide')
浏览器标签页之间的同步
默认情况下,store 的变更会在浏览器标签页之间同步。
有一个 listen 选项可以禁用同步。
import { persistentAtom } from '@nanostores/persistent'
export const $draft = persistentAtom('draft', '', { listen: false })
值编码
encode 和 decode 选项可以设置为在设置值之前或从持久化存储获取值之后处理该值。
import { persistentAtom } from '@nanostores/persistent'
export const $draft = persistentAtom('draft', [], {
encode(value) {
return JSON.stringify(value)
},
decode(value) {
try {
return JSON.parse(value)
} catch() {
return value
}
}
})
服务端渲染
该 store 内置了 SSR 支持。在服务器上,它们将使用
空对象代替 localStorage。
你可以使用特定数据手动初始化 store:
if (isServer) {
$locale.set(user.locale)
}
持久化引擎
您可以将 localStorage 切换到任何其他存储,以用于所有已使用的存储。
import { setPersistentEngine } from '@nanostores/persistent'
let listeners = []
function onChange(key, newValue) {
const event = { key, newValue }
for (const i of listeners) i(event)
}
// Must implement storage[key] = value, storage[key], and delete storage[key]
const storage = new Proxy(
{},
{
set(target, name, value) {
target[name] = value
onChange(name, value)
},
get(target, name) {
return target[name]
},
deleteProperty(target, name) {
delete target[name]
onChange(name, undefined)
}
}
)
// Must implement addEventListener and removeEventListener
const events = {
addEventListener(key, callback) {
listeners.push(callback)
},
removeEventListener(key, callback) {
listeners = listeners.filter(i => i !== callback)
},
// window dispatches "storage" events for any key change
// => One listener for all map keys is enough
perKey: false
}
setPersistentEngine(storage, events)
你无需为服务端渲染做任何操作。它默认受支持。
你只需在具有浏览器标签页的环境中,
或出于其他存储同步原因时,指定 events.addEventListener
和 events.removeEventListener 的主体。
perKey 使 PersistentMap 除了为所有键添加一个监听器外,
还为每个键添加一个监听器。当键变更事件仅针对特定订阅的键
分发时,这一点是相关的。
对于 TypeScript,我们为事件对象提供了 PersistentListener 和 PersistentEvent 类型。
import { PersistentListener, PersistentEvent } from '@nanostores/persistent'
const events = {
addEventListener(key: string, callback: PersistentListener) {
…
},
removeEventListener(key: string, callback: PersistentListener) {
…
}
}
function onChange() {
const event: PersistentEvent = {
key: 'locale' // Changed storage key
newValue: 'ru'
}
…
}
测试
有一个特殊的 API,用于将 localStorage 替换为带有更改键和获取所有值辅助函数的伪存储引擎。
import {
useTestStorageEngine,
setTestStorageKey,
cleanTestStorage,
getTestStorage
} from '@nanostores/persistent'
import { $settings } from './storage.js'
beforeAll(() => {
useTestStorageEngine()
})
afterEach(() => {
cleanTestStorage()
})
it('listens for changes', () => {
setTestStorageKey('settings:locale', 'ru')
expect($settings.get()).toEqual({ locale: 'ru' })
})
it('changes storage', () => {
$settings.setKey('locale')
expect(getTestStorage()).toEqual({ 'settings:locale': 'ru' })
})