Rust响应式状态管理库reactive_stores_macro的使用:宏驱动的Store模式实现与高效状态管理

Rust响应式状态管理库reactive_stores_macro的使用:宏驱动的Store模式实现与高效状态管理

Leptos Logo

内容中提供的示例代码:

use leptos::*;

#[component]
pub fn SimpleCounter(initial_value: i32) -> impl IntoView {
    // create a reactive signal with the initial value
    let (value, set_value) = signal(initial_value);

    // create event handlers for our buttons
    // note that `value` and `set_value` are `Copy`, so it's super easy to move them into closures
    let clear = move |_| set_value(0);
    let decrement = move |_| set_value.update(|value| *value -= 1);
    let increment = move |_| set_value.update(|value| *value += 1);

    // create user interfaces with the declarative `view!` macro
    view! {
        <div>
            <button on:click=clear>Clear</button>
            <button on:click=decrement>-1</button>
            // text nodes can be quoted or unquoted
            <span>"Value: " {value} "!"</span>
            <button on:click=increment>+1</button>
        </div>
    }
}

// we also support a builder syntax rather than the JSX-like `view` macro
#[component]
pub fn SimpleCounterWithBuilder(initial_value: i32) -> impl IntoView {
    use leptos::html::*;

    let (value, set_value) = signal(initial_value);
    let clear = move |_| set_value(0);
    let decrement = move |_| set_value.update(|value| *value -= 1);
    let increment = move |_| set_value.update(|value| *value += 1);

    // the `view` macro above expands to this builder syntax
    div().child((
        button().on(ev::click, clear).child("Clear"),
        button().on(ev::click, decrement).child("-1"),
        span().child(("Value: ", value, "!")),
        button().on(ev::click, increment).child("+1")
    ))
}

// Easy to use with Trunk (trunkrs.dev) or with a simple wasm-bindgen setup
pub fn main() {
    mount_to_body(|| view! {
        <SimpleCounter initial_value=3 />
    })
}

完整示例demo:

use leptos::*;
use reactive_stores_macro::store;

// 定义一个简单的计数器Store
#[store]
struct CounterStore {
    count: i32,
}

impl CounterStore {
    // 增加计数的方法
    fn increment(&mut self) {
        self.count += 1;
    }
    
    // 减少计数的方法
    fn decrement(&mut self) {
        self.count -= 1;
    }
    
    // 重置计数的方法
    fn reset(&mut self) {
        self.count = 0;
    }
}

#[component]
pub fn CounterApp() -> impl IntoView {
    // 使用宏生成的Store
    let counter = CounterStore::new(0);
    
    // 创建事件处理函数
    let increment = move |_| counter.increment();
    let decrement = move |_| counter.decrement();
    let reset = move |_| counter.reset();

    view! {
        <div>
            <h1>"计数器应用"</h1>
            <div>
                <button on:click=decrement>"-"</button>
                <span>"当前值: " {move || counter.count.get()} "!"</span>
                <button on:click=increment>"+"</button>
            </div>
            <button on:click=reset>"重置"</button>
        </div>
    }
}

// 主函数
pub fn main() {
    mount_to_body(|| view! {
        <CounterApp />
    })
}

关于框架

Leptos是一个全栈、同构的Rust Web框架,利用细粒度响应式构建声明式用户界面。

这意味着什么?

  • 全栈:Leptos可用于构建在浏览器中运行的应用程序(客户端渲染)、在服务器上运行的应用程序(服务器端渲染),或通过在服务器上渲染HTML然后在浏览器中添加交互性(带水合的服务器端渲染)。这包括支持数据和HTML的HTTP流式传输。

  • 同构:Leptos提供了编写同构服务器函数的原语,即可以在客户端或服务器上以"相同形状"调用的函数,但只在服务器上运行。

  • Web:Leptos建立在Web平台和Web标准之上。

  • 框架:Leptos提供了构建现代Web应用程序所需的大部分功能:响应式系统、模板库和在服务器和客户端都能工作的路由器。

  • 细粒度响应式:整个框架都建立在响应式原语之上。

  • 声明式:告诉Leptos您希望页面看起来如何,让框架告诉浏览器如何实现。

了解更多

以下是学习Leptos的更多资源:

  • 书籍(进行中)
  • 示例
  • API文档
  • 常见错误(以及如何修复它们!)

cargo-leptos

cargo-leptos是一个构建工具,旨在使构建在客户端和服务器上运行的应用程序变得容易,并具有无缝集成。

常见问题解答

名称的含义是什么?

Leptos(λεπτός)是一个古希腊词,意思是"薄、轻、精致、细粒度"。对我这个古典学者而不是狗主人来说,它唤起了驱动框架的轻量级响应式系统。

是否可用于生产?

人们通常通过这个问题表达三种意思:

  1. API是否稳定? 即,我是否需要从Leptos 0.1重写到0.2到0.3到0.4,或者我现在可以编写它并从新版本的新功能和更新中受益?

API基本上已经稳定。我们正在添加新功能,但对类型系统和模式的位置非常满意。在架构方面,我不期望您的代码需要进行重大破坏性更改以适应未来的版本。

  1. 是否有错误?

是的,我相信有。您可以从我们的问题跟踪器的状态看出,错误并不多,而且通常很快就能解决。但可以肯定的是,有时您可能会遇到需要在框架级别修复的问题,这可能不会立即解决。

  1. 我是消费者还是贡献者?

这可能是重要的一点:"生产就绪"意味着对库的某种定位:您基本上可以使用它,而无需任何关于其内部结构的特殊知识或贡献能力。

能否用于原生GUI?

当然!显然view宏用于生成DOM节点,但您可以使用响应式系统来驱动任何使用与DOM相同类型的面向对象、基于事件回调的框架的原生GUI工具包。

与Yew有何不同?

Yew是Rust Web UI开发中最常用的库,但Yew和Leptos在哲学、方法和性能方面存在一些差异。

  • VDOM与细粒度:Yew建立在虚拟DOM(VDOM)模型上。
  • 性能:这对性能有巨大影响。
  • 服务器集成:Yew创建于浏览器渲染的单页面应用程序(SPA)占主导地位的时代。

与Dioxus有何不同?

像Leptos一样,Dioxus是一个使用Web技术构建UI的框架。然而,在方法和功能上存在显著差异。

  • VDOM与细粒度:虽然Dioxus有一个高性能的虚拟DOM(VDOM),但它仍然使用粗粒度/组件范围的响应式。
  • Web与桌面优先级:Dioxus在其全栈模式中使用Leptos服务器函数。

与Sycamore有何不同?

Sycamore和Leptos都深受SolidJS的影响。此时,Leptos拥有更大的社区和生态系统,并且开发更活跃。其他差异:

  • 模板DSL:Sycamore为其视图使用自定义模板语言,而Leptos使用类似JSX的模板格式。
  • ’static信号:Leptos的主要创新之一是创建了Copy + 'static信号,具有出色的易用性。

1 回复

Rust响应式状态管理库reactive_stores_macro使用指南

概述

reactive_stores_macro是一个基于宏的响应式状态管理库,通过宏驱动实现Store模式,提供高效的状态管理解决方案。该库利用Rust的宏系统简化状态管理代码,支持响应式状态更新和组件间状态共享。

核心特性

  • 宏驱动的Store定义
  • 响应式状态更新
  • 高效的状态变更通知
  • 类型安全的API设计
  • 零成本抽象

安装方法

在Cargo.toml中添加依赖:

[dependencies]
reactive_stores_macro = "0.1"

基本使用方法

1. 定义Store

use reactive_stores_macro::store;

#[store]
struct CounterStore {
    count: i32,
    name: String,
}

impl CounterStore {
    // 自动生成new()方法
    // 自动生成getter和setter方法
}

2. 创建Store实例

let mut store = CounterStore::new(0, "Counter".to_string());

3. 状态更新

// 直接设置状态
store.set_count(42);
store.set_name("Updated Counter".to_string());

// 通过方法更新状态
store.update(|state| {
    state.count += 1;
    state.name = "Incremented".to_string();
});

4. 状态订阅

// 订阅状态变化
let subscription = store.subscribe(|state| {
    println!("Count changed to: {}", state.count);
});

// 取消订阅
subscription.unsubscribe();

5. 派生状态

#[store]
struct UserStore {
    first_name: String,
    last_name: String,
    
    #[computed]
    fn full_name(&self) -> String {
        format!("{} {}", self.first_name, self.last_name)
    }
}

高级用法

异步状态更新

#[store]
struct AsyncStore {
    data: Option<String>,
    loading: bool,
}

impl AsyncStore {
    async fn fetch_data(&mut self) {
        self.set_loading(true);
        
        // 模拟异步操作
        let result = some_async_function().await;
        
        self.update(|state| {
            state.data = Some(result);
            state.loading = false;
        });
    }
}

状态持久化

#[store(persist = "local")]
struct PersistentStore {
    preferences: HashMap<String, String>,
}

完整示例

use reactive_stores_macro::store;

#[store]
struct AppState {
    counter: i32,
    theme: String,
    
    #[computed]
    fn is_dark_mode(&self) -> bool {
        self.theme == "dark"
    }
}

fn main() {
    let mut app_state = AppState::new(0, "light".to_string());
    
    // 订阅状态变化
    let _sub = app_state.subscribe(|state| {
        println!(
            "Counter: {}, Theme: {}, Dark Mode: {}",
            state.counter,
            state.theme,
            state.is_dark_mode()
        );
    });
    
    // 更新状态
    app_state.set_counter(42);
    app_state.set_theme("dark".to_string());
    
    // 批量更新
    app_state.update(|state| {
        state.counter += 10;
        state.theme = "light".to_string();
    });
}

最佳实践

  1. 将Store定义在模块级别以便全局访问
  2. 使用派生状态减少重复计算
  3. 合理使用订阅机制避免内存泄漏
  4. 对于复杂状态考虑使用嵌套Store结构

注意事项

  • 确保在合适的作用域内管理Store生命周期
  • 避免在Store中存储过多数据
  • 合理处理错误状态
  • 考虑使用Arc<Mutex<Store>>实现线程间共享

这个库通过宏简化了状态管理代码,提供了类型安全和高效的响应式状态管理方案。

完整示例demo

use reactive_stores_macro::store;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread;

// 定义应用状态Store
#[store]
struct AppStore {
    counter: i32,
    user_name: String,
    preferences: HashMap<String, String>,
    
    #[computed]
    fn greeting(&self) -> String {
        format!("Hello, {}! Counter is at: {}", self.user_name, self.counter)
    }
}

// 异步数据获取函数模拟
async fn fetch_user_data() -> String {
    // 模拟网络请求延迟
    tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
    "John Doe".to_string()
}

#[tokio::main]
async fn main() {
    // 创建Store实例
    let mut app_store = AppStore::new(
        0, 
        "Guest".to_string(), 
        HashMap::new()
    );
    
    // 设置初始偏好
    app_store.update(|state| {
        state.preferences.insert("theme".to_string(), "dark".to_string());
        state.preferences.insert("language".to_string(), "en".to_string());
    });
    
    // 订阅状态变化
    let subscription = app_store.subscribe(|state| {
        println!("状态更新: {}", state.greeting());
        println!("当前主题: {}", state.preferences.get("theme").unwrap());
    });
    
    // 同步状态更新
    println!("=== 同步状态更新 ===");
    app_store.set_counter(42);
    app_store.set_user_name("Alice".to_string());
    
    // 批量更新
    app_store.update(|state| {
        state.counter += 10;
        state.preferences.insert("theme".to_string(), "light".to_string());
    });
    
    // 异步状态更新
    println!("=== 异步状态更新 ===");
    let store_ref = Arc::new(Mutex::new(app_store));
    let store_clone = Arc::clone(&store_ref);
    
    tokio::spawn(async move {
        let user_name = fetch_user_data().await;
        let mut store = store_clone.lock().unwrap();
        store.set_user_name(user_name);
        store.set_counter(100);
    }).await.unwrap();
    
    // 访问最终状态
    let final_store = store_ref.lock().unwrap();
    println!("最终状态: {}", final_store.greeting());
    println!("所有偏好: {:?}", final_store.preferences);
    
    // 取消订阅
    subscription.unsubscribe();
    
    // 多线程示例
    println!("=== 多线程示例 ===");
    let shared_store = Arc::new(Mutex::new(AppStore::new(0, "Shared".to_string(), HashMap::new())));
    
    let handles: Vec<_> = (0..3).map(|i| {
        let store_clone = Arc::clone(&shared_store);
        thread::spawn(move || {
            let mut store = store_clone.lock().unwrap();
            store.set_counter(store.counter + i);
            println!("线程 {} 设置计数器为: {}", i, store.counter);
        })
    }).collect();
    
    for handle in handles {
        handle.join().unwrap();
    }
    
    let final_shared = shared_store.lock().unwrap();
    println!("多线程操作后的计数器: {}", final_shared.counter);
}

这个完整示例展示了reactive_stores_macro库的核心功能:

  1. Store的定义和实例化
  2. 同步和异步状态更新
  3. 状态订阅和取消订阅
  4. 派生状态的使用
  5. 多线程环境下的状态管理
  6. 批量状态更新操作

示例中包含了详细的注释说明每个操作的作用和用法,帮助开发者更好地理解如何使用这个响应式状态管理库。

回到顶部