Leptos本地存储:localStorage与IndexedDB实战指南

Leptos本地存储:localStorage与IndexedDB实战指南

【免费下载链接】leptos Build fast web applications with Rust. 【免费下载链接】leptos 项目地址: https://gitcode.com/GitHub_Trending/le/leptos

在现代化Web应用开发中,本地数据存储是提升用户体验的关键技术。Leptos作为Rust生态中的全栈Web框架,提供了优雅的方式来处理浏览器本地存储。本文将深入探讨如何在Leptos应用中高效使用localStorage和IndexedDB,并提供完整的实战示例。

本地存储技术对比

特性localStorageIndexedDB
存储容量5-10MB50%磁盘空间
数据类型字符串结构化数据
查询能力简单键值复杂索引查询
事务支持完整ACID事务
异步操作同步异步
适用场景简单配置、令牌复杂应用数据

localStorage在Leptos中的使用

基础API封装

use leptos::*;
use web_sys::Storage;

#[derive(Clone)]
pub struct LocalStorageService {
    storage: Option<Storage>,
}

impl LocalStorageService {
    pub fn new() -> Self {
        let storage = window()
            .local_storage()
            .ok()
            .flatten();
        Self { storage }
    }

    pub fn set_item(&self, key: &str, value: &str) -> Result<(), String> {
        if let Some(storage) = &self.storage {
            storage.set_item(key, value)
                .map_err(|e| format!("Failed to set item: {:?}", e))
        } else {
            Err("LocalStorage not available".into())
        }
    }

    pub fn get_item(&self, key: &str) -> Option<String> {
        self.storage.as_ref()
            .and_then(|storage| storage.get_item(key).ok().flatten())
    }

    pub fn remove_item(&self, key: &str) -> Result<(), String> {
        if let Some(storage) = &self.storage {
            storage.remove_item(key)
                .map_err(|e| format!("Failed to remove item: {:?}", e))
        } else {
            Err("LocalStorage not available".into())
        }
    }
}

响应式集成示例

#[component]
pub fn UserPreferences() -> impl IntoView {
    let storage = LocalStorageService::new();
    let (theme, set_theme) = signal("light".to_string());

    // 初始化时从localStorage加载
    Effect::new(move |_| {
        if let Some(saved_theme) = storage.get_item("theme") {
            set_theme.set(saved_theme);
        }
    });

    // 主题变化时保存到localStorage
    Effect::new(move |_| {
        let current_theme = theme.get();
        if let Err(e) = storage.set_item("theme", &current_theme) {
            logging::error!("Failed to save theme: {}", e);
        }
    });

    view! {
        <div>
            <h2>"主题设置"</h2>
            <select
                value=theme
                on:change=move |ev| {
                    let value = event_target_value(&ev);
                    set_theme.set(value);
                }
            >
                <option value="light">"浅色"</option>
                <option value="dark">"深色"</option>
                <option value="auto">"自动"</option>
            </select>
        </div>
    }
}

IndexedDB高级存储方案

数据库封装类

use leptos::*;
use gloo_storage::{Storage, IndexedDbStorage};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserData {
    pub id: String,
    pub preferences: HashMap<String, String>,
    pub last_updated: i64,
}

pub struct IndexedDbService {
    db_name: String,
    version: u32,
}

impl IndexedDbService {
    pub async fn new(db_name: &str, version: u32) -> Result<Self, String> {
        // 在实际应用中需要实现完整的IndexedDB初始化
        Ok(Self {
            db_name: db_name.to_string(),
            version,
        })
    }

    pub async fn save_user_data(&self, data: &UserData) -> Result<(), String> {
        // 模拟保存操作
        let storage = IndexedDbStorage::new(&self.db_name, self.version)
            .await
            .map_err(|e| format!("Failed to open DB: {:?}", e))?;
        
        storage.set(&data.id, data)
            .await
            .map_err(|e| format!("Failed to save data: {:?}", e))
    }

    pub async fn get_user_data(&self, id: &str) -> Result<Option<UserData>, String> {
        // 模拟读取操作
        let storage = IndexedDbStorage::new(&self.db_name, self.version)
            .await
            .map_err(|e| format!("Failed to open DB: {:?}", e))?;
        
        storage.get(id)
            .await
            .map_err(|e| format!("Failed to get data: {:?}", e))
    }
}

异步资源集成

#[component]
pub fn UserProfile(user_id: String) -> impl IntoView {
    let db_service = IndexedDbService::new("user_db", 1);
    let user_data = Resource::new(
        move || user_id.clone(),
        move |id| {
            let db = db_service.clone();
            async move {
                db.get_user_data(&id).await.ok().flatten()
            }
        }
    );

    view! {
        <div>
            <h2>"用户资料"</h2>
            <Suspense fallback=move || view! { <p>"加载中..."</p> }>
                {move || user_data.get().map(|data| match data {
                    Some(user_data) => view! {
                        <div>
                            <p>{format!("用户ID: {}", user_data.id)}</p>
                            <p>{format!("最后更新: {}", user_data.last_updated)}</p>
                            // 显示偏好设置
                        </div>
                    }.into_view(),
                    None => view! { <p>"用户数据不存在"</p> }.into_view(),
                })}
            </Suspense>
        </div>
    }
}

实战:Todo应用数据持久化

基于Leptos官方TodoMVC示例,我们实现完整的数据持久化方案:

use leptos::{ev, html::Input, prelude::*};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

const STORAGE_KEY: &str = "todos-leptos";

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Todo {
    pub id: Uuid,
    pub title: String,
    pub completed: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Todos(pub Vec<Todo>);

impl Default for Todos {
    fn default() -> Self {
        // 从localStorage加载初始数据
        if let Ok(Some(storage)) = window().local_storage() {
            if let Ok(Some(json)) = storage.get_item(STORAGE_KEY) {
                if let Ok(todos) = serde_json::from_str::<Vec<Todo>>(&json) {
                    return Self(todos);
                }
            }
        }
        Self(Vec::new())
    }
}

#[component]
pub fn PersistentTodoApp() -> impl IntoView {
    let (todos, set_todos) = signal(Todos::default());

    // 数据变化时自动保存到localStorage
    Effect::new(move |_| {
        if let Ok(Some(storage)) = window().local_storage() {
            if let Ok(json) = serde_json::to_string(&todos.get()) {
                if storage.set_item(STORAGE_KEY, &json).is_err() {
                    logging::error!("Failed to save todos to localStorage");
                }
            }
        }
    });

    // 添加其他Todo应用逻辑...
    view! {
        /* Todo应用UI */
    }
}

性能优化与最佳实践

1. 防抖机制

use std::time::Duration;

pub struct DebouncedStorage {
    storage: LocalStorageService,
    timeout: Option<Timeout>,
}

impl DebouncedStorage {
    pub fn new() -> Self {
        Self {
            storage: LocalStorageService::new(),
            timeout: None,
        }
    }

    pub fn set_item_debounced(&mut self, key: String, value: String, delay: Duration) {
        // 清除之前的超时
        if let Some(timeout) = self.timeout.take() {
            timeout.clear();
        }

        // 设置新的超时
        let storage = self.storage.clone();
        self.timeout = Some(Timeout::new(delay, move || {
            if let Err(e) = storage.set_item(&key, &value) {
                logging::error!("Debounced save failed: {}", e);
            }
        }));
    }
}

2. 错误处理策略

pub enum StorageError {
    Unavailable,
    QuotaExceeded,
    SerializationError,
    Unknown(String),
}

impl From<JsValue> for StorageError {
    fn from(value: JsValue) -> Self {
        let msg = value.as_string().unwrap_or_default();
        if msg.contains("quota") {
            StorageError::QuotaExceeded
        } else {
            StorageError::Unknown(msg)
        }
    }
}

pub trait FallbackStorage {
    fn set_item_with_fallback(&self, key: &str, value: &str) -> Result<(), StorageError>;
}

impl FallbackStorage for LocalStorageService {
    fn set_item_with_fallback(&self, key: &str, value: &str) -> Result<(), StorageError> {
        match self.set_item(key, value) {
            Ok(()) => Ok(()),
            Err(StorageError::QuotaExceeded) => {
                // 实现LRU淘汰策略
                self.cleanup_old_items()?;
                self.set_item(key, value)
            }
            Err(e) => Err(e),
        }
    }
}

数据迁移方案

mermaid

const DATA_VERSION: &str = "data_version";

pub struct DataMigrator {
    storage: LocalStorageService,
    current_version: u32,
}

impl DataMigrator {
    pub async fn migrate_if_needed(&self) -> Result<(), String> {
        let stored_version: u32 = self.storage.get_item(DATA_VERSION)
            .and_then(|v| v.parse().ok())
            .unwrap_or(0);

        if stored_version < self.current_version {
            self.perform_migration(stored_version, self.current_version).await?;
            self.storage.set_item(DATA_VERSION, &self.current_version.to_string())?;
        }
        Ok(())
    }

    async fn perform_migration(&self, from: u32, to: u32) -> Result<(), String> {
        for version in from..to {
            match version {
                0 => self.migrate_v0_to_v1().await?,
                1 => self.migrate_v1_to_v2().await?,
                _ => return Err(format!("Unknown migration from version {}", version)),
            }
        }
        Ok(())
    }
}

总结

Leptos框架为本地存储提供了强大的集成能力:

  1. localStorage:适合简单键值对数据,配置信息,用户偏好设置
  2. IndexedDB:适合复杂结构化数据,需要事务支持的应用
  3. 响应式集成:通过Effect自动同步信号状态与存储
  4. 错误恢复:完善的错误处理和降级方案
  5. 数据迁移:版本化数据管理确保向前兼容

通过合理选择存储方案和实现最佳实践,可以构建出既快速又可靠的前端应用。Leptos的细粒度响应式系统与浏览器存储API的完美结合,为开发者提供了构建现代化Web应用的强大工具集。

【免费下载链接】leptos Build fast web applications with Rust. 【免费下载链接】leptos 项目地址: https://gitcode.com/GitHub_Trending/le/leptos

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值