本地存储 #

一、存储方案概述 #

1.1 存储方案对比 #

方案 容量 持久化 性能 使用场景
localStorage ~5MB 简单键值存储
sessionStorage ~5MB 会话临时数据
IndexedDB 复杂数据结构
tauri-plugin-store 无限制 应用配置存储

1.2 存储架构 #

text
┌─────────────────────────────────────────────────────────────┐
│                      存储架构                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐        │
│  │ localStorage│  │sessionStorage│  │ IndexedDB   │        │
│  │             │  │             │  │             │        │
│  │  WebView    │  │  WebView    │  │  WebView    │        │
│  │  存储       │  │  存储       │  │  存储       │        │
│  └─────────────┘  └─────────────┘  └─────────────┘        │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              tauri-plugin-store                     │   │
│  │                                                     │   │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐│   │
│  │  │  JSON 文件  │  │  加密存储   │  │  自动同步   ││   │
│  │  └─────────────┘  └─────────────┘  └─────────────┘│   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

二、localStorage #

2.1 基本使用 #

typescript
// 存储数据
localStorage.setItem('theme', 'dark');
localStorage.setItem('fontSize', '16');

// 读取数据
const theme = localStorage.getItem('theme');
const fontSize = localStorage.getItem('fontSize');

// 删除数据
localStorage.removeItem('theme');

// 清空所有数据
localStorage.clear();

2.2 存储对象 #

typescript
// 存储对象
const user = { name: 'Alice', email: 'alice@example.com' };
localStorage.setItem('user', JSON.stringify(user));

// 读取对象
const storedUser = JSON.parse(localStorage.getItem('user') || 'null');

2.3 封装工具函数 #

typescript
// utils/storage.ts
export const storage = {
    get<T>(key: string, defaultValue: T): T {
        const value = localStorage.getItem(key);
        if (value === null) {
            return defaultValue;
        }
        try {
            return JSON.parse(value) as T;
        } catch {
            return value as unknown as T;
        }
    },

    set<T>(key: string, value: T): void {
        localStorage.setItem(key, JSON.stringify(value));
    },

    remove(key: string): void {
        localStorage.removeItem(key);
    },

    clear(): void {
        localStorage.clear();
    },
};

2.4 React Hook #

typescript
import { useState, useEffect } from 'react';

export function useLocalStorage<T>(key: string, initialValue: T) {
    const [storedValue, setStoredValue] = useState<T>(() => {
        const item = localStorage.getItem(key);
        return item ? JSON.parse(item) : initialValue;
    });

    const setValue = (value: T | ((val: T) => T)) => {
        const valueToStore = value instanceof Function ? value(storedValue) : value;
        setStoredValue(valueToStore);
        localStorage.setItem(key, JSON.stringify(valueToStore));
    };

    return [storedValue, setValue] as const;
}

三、sessionStorage #

3.1 基本使用 #

typescript
// 存储会话数据
sessionStorage.setItem('sessionToken', 'abc123');

// 读取会话数据
const token = sessionStorage.getItem('sessionToken');

// 删除会话数据
sessionStorage.removeItem('sessionToken');

3.2 会话管理 #

typescript
class SessionManager {
    private static TOKEN_KEY = 'session_token';
    private static USER_KEY = 'session_user';

    static setSession(token: string, user: User) {
        sessionStorage.setItem(this.TOKEN_KEY, token);
        sessionStorage.setItem(this.USER_KEY, JSON.stringify(user));
    }

    static getToken(): string | null {
        return sessionStorage.getItem(this.TOKEN_KEY);
    }

    static getUser(): User | null {
        const user = sessionStorage.getItem(this.USER_KEY);
        return user ? JSON.parse(user) : null;
    }

    static clearSession() {
        sessionStorage.removeItem(this.TOKEN_KEY);
        sessionStorage.removeItem(this.USER_KEY);
    }

    static isAuthenticated(): boolean {
        return !!this.getToken();
    }
}

四、IndexedDB #

4.1 基本使用 #

typescript
// 打开数据库
const request = indexedDB.open('MyAppDB', 1);

request.onupgradeneeded = (event) => {
    const db = (event.target as IDBOpenDBRequest).result;
    
    if (!db.objectStoreNames.contains('users')) {
        db.createObjectStore('users', { keyPath: 'id' });
    }
    
    if (!db.objectStoreNames.contains('posts')) {
        const store = db.createObjectStore('posts', { keyPath: 'id' });
        store.createIndex('authorId', 'authorId', { unique: false });
    }
};

request.onsuccess = (event) => {
    const db = (event.target as IDBOpenDBRequest).result;
    console.log('Database opened successfully');
};

4.2 封装 IndexedDB #

typescript
// utils/indexedDB.ts
export class IndexedDBHelper {
    private db: IDBDatabase | null = null;

    async open(name: string, version: number, stores: string[]): Promise<IDBDatabase> {
        return new Promise((resolve, reject) => {
            const request = indexedDB.open(name, version);

            request.onupgradeneeded = (event) => {
                const db = (event.target as IDBOpenDBRequest).result;
                stores.forEach((store) => {
                    if (!db.objectStoreNames.contains(store)) {
                        db.createObjectStore(store, { keyPath: 'id' });
                    }
                });
            };

            request.onsuccess = (event) => {
                this.db = (event.target as IDBOpenDBRequest).result;
                resolve(this.db);
            };

            request.onerror = () => {
                reject(request.error);
            };
        });
    }

    async add<T>(storeName: string, data: T): Promise<void> {
        return this.transaction(storeName, 'readwrite', (store) => {
            store.add(data);
        });
    }

    async get<T>(storeName: string, key: IDBValidKey): Promise<T | undefined> {
        return new Promise((resolve, reject) => {
            const transaction = this.db!.transaction(storeName, 'readonly');
            const store = transaction.objectStore(storeName);
            const request = store.get(key);

            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    }

    async getAll<T>(storeName: string): Promise<T[]> {
        return new Promise((resolve, reject) => {
            const transaction = this.db!.transaction(storeName, 'readonly');
            const store = transaction.objectStore(storeName);
            const request = store.getAll();

            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    }

    async put<T>(storeName: string, data: T): Promise<void> {
        return this.transaction(storeName, 'readwrite', (store) => {
            store.put(data);
        });
    }

    async delete(storeName: string, key: IDBValidKey): Promise<void> {
        return this.transaction(storeName, 'readwrite', (store) => {
            store.delete(key);
        });
    }

    private async transaction(
        storeName: string,
        mode: IDBTransactionMode,
        operation: (store: IDBObjectStore) => void
    ): Promise<void> {
        return new Promise((resolve, reject) => {
            const transaction = this.db!.transaction(storeName, mode);
            const store = transaction.objectStore(storeName);

            operation(store);

            transaction.oncomplete = () => resolve();
            transaction.onerror = () => reject(transaction.error);
        });
    }
}

五、tauri-plugin-store #

5.1 安装插件 #

bash
pnpm add @tauri-apps/plugin-store
rust
// src-tauri/src/lib.rs
tauri::Builder::default()
    .plugin(tauri_plugin_store::Builder::default().build())
    .run(tauri::generate_context!())
    .expect("error while running tauri application");

5.2 基本使用 #

typescript
import { Store } from '@tauri-apps/plugin-store';

// 创建存储实例
const store = new Store('app-store.json');

// 存储数据
await store.set('theme', 'dark');
await store.set('user', { name: 'Alice', email: 'alice@example.com' });

// 读取数据
const theme = await store.get<string>('theme');
const user = await store.get<{ name: string; email: string }>('user');

// 删除数据
await store.delete('theme');

// 保存到磁盘
await store.save();

// 清空所有数据
await store.clear();

5.3 自动保存 #

typescript
// 启用自动保存
const store = new Store('app-store.json', { autoSave: true });

// 数据变更会自动保存
await store.set('theme', 'dark'); // 自动保存到磁盘

5.4 监听变更 #

typescript
import { Store } from '@tauri-apps/plugin-store';

const store = new Store('app-store.json');

// 监听变更
const unlisten = store.onChange((key, value) => {
    console.log(`Key "${key}" changed to:`, value);
});

// 取消监听
unlisten();

5.5 存储管理器 #

typescript
import { Store } from '@tauri-apps/plugin-store';

class StoreManager {
    private stores: Map<string, Store> = new Map();

    async getStore(name: string): Promise<Store> {
        if (!this.stores.has(name)) {
            const store = new Store(`${name}.json`);
            await store.load();
            this.stores.set(name, store);
        }
        return this.stores.get(name)!;
    }

    async get<T>(storeName: string, key: string): Promise<T | null> {
        const store = await this.getStore(storeName);
        return store.get<T>(key);
    }

    async set<T>(storeName: string, key: string, value: T): Promise<void> {
        const store = await this.getStore(storeName);
        await store.set(key, value);
        await store.save();
    }

    async delete(storeName: string, key: string): Promise<void> {
        const store = await this.getStore(storeName);
        await store.delete(key);
        await store.save();
    }
}

export const storeManager = new StoreManager();

六、最佳实践 #

6.1 存储键命名 #

typescript
// 使用命名空间避免冲突
const STORAGE_KEYS = {
    THEME: 'app:theme',
    USER: 'app:user',
    SETTINGS: 'app:settings',
    RECENT_FILES: 'app:recent_files',
} as const;

6.2 数据版本管理 #

typescript
interface StoredData<T> {
    version: number;
    data: T;
    updatedAt: string;
}

const CURRENT_VERSION = 1;

function saveWithVersion<T>(key: string, data: T): void {
    const stored: StoredData<T> = {
        version: CURRENT_VERSION,
        data,
        updatedAt: new Date().toISOString(),
    };
    localStorage.setItem(key, JSON.stringify(stored));
}

function loadWithVersion<T>(key: string): T | null {
    const raw = localStorage.getItem(key);
    if (!raw) return null;

    const stored: StoredData<T> = JSON.parse(raw);
    
    // 版本迁移
    if (stored.version < CURRENT_VERSION) {
        return migrateData(stored);
    }
    
    return stored.data;
}

6.3 数据加密 #

typescript
import { invoke } from '@tauri-apps/api/core';

class SecureStorage {
    static async encrypt(data: string): Promise<string> {
        return invoke('encrypt_data', { data });
    }

    static async decrypt(encrypted: string): Promise<string> {
        return invoke('decrypt_data', { data: encrypted });
    }

    static async setSecure(key: string, value: unknown): Promise<void> {
        const encrypted = await this.encrypt(JSON.stringify(value));
        localStorage.setItem(key, encrypted);
    }

    static async getSecure<T>(key: string): Promise<T | null> {
        const encrypted = localStorage.getItem(key);
        if (!encrypted) return null;

        const decrypted = await this.decrypt(encrypted);
        return JSON.parse(decrypted);
    }
}

七、总结 #

7.1 核心要点 #

要点 说明
localStorage 简单键值存储
sessionStorage 会话临时存储
IndexedDB 复杂数据存储
tauri-plugin-store 文件持久化存储

7.2 下一步 #

现在你已经掌握了本地存储,接下来让我们学习 文件系统,了解如何在 Tauri 中进行文件操作!

最后更新:2026-03-28