ITADN
nanostores/persistent
README.md
以下内容由 AI 翻译,如有问题请点此提交 issue 反馈

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

persistentJSONpersistentAtom 的一个便捷快捷方式,它会为您处理 JSON 序列化。

它支持任何有效的 JSON 类型:对象、数组、原始值(stringnumberbooleannull)。

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:sidebarsettings:theme 键中保存值。

您可以通过 setKey 方法更改键:

$settings.setKey('sidebar', 'hide')

浏览器标签页之间的同步

默认情况下,store 的变更会在浏览器标签页之间同步。

有一个 listen 选项可以禁用同步。

import { persistentAtom } from '@nanostores/persistent'

export const $draft = persistentAtom('draft', '', { listen: false })

值编码

encodedecode 选项可以设置为在设置值之前或从持久化存储获取值之后处理该值。

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.addEventListenerevents.removeEventListener 的主体。

perKey 使 PersistentMap 除了为所有键添加一个监听器外, 还为每个键添加一个监听器。当键变更事件仅针对特定订阅的键 分发时,这一点是相关的。

对于 TypeScript,我们为事件对象提供了 PersistentListenerPersistentEvent 类型。

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' })
})