全局状态管理

本案例演示了如何在 Rust 终端应用中,利用 ratatui-kit 的 Store 全局状态管理机制,实现跨页面共享和同步数据。用户可以在不同页面间切换,计数器和输入框的内容始终保持同步。通过本示例,你可以学习到 Store 的定义与使用、全局状态的读写、页面间数据共享,以及如何构建响应式的终端多页面应用。

use ratatui::{
    style::{Style, Stylize},
    text::Line,
};
use ratatui_kit::ratatui;
use ratatui_kit::{
    crossterm::event::{Event, KeyCode, KeyEventKind},
    prelude::*,
    ratatui::layout::Constraint,
};

#[derive(Store)]
pub struct CounterAndTextInput {
    pub count: i32,
    pub value: String,
}

impl Default for CounterAndTextInput {
    fn default() -> Self {
        Self {
            count: 0,
            value: String::new(),
        }
    }
}

#[tokio::main]
async fn main() {
    let routes = routes! {
        "/" => HomePage,
        "/counter" => CounterPage,
        "/input" => InputPage,

    };

    element!(RouterProvider(
        routes:routes,
        index_path:"/",
    ))
    .fullscreen()
    .await
    .expect("Failed to run the application");
}

#[component]
fn HomePage(mut hooks: Hooks) -> impl Into<AnyElement<'static>> {
    let store = &COUNTER_AND_TEXT_INPUT_STORE;
    let (count, value) = use_stores!(store.count, store.value);
    let mut navigate = hooks.use_navigate();
    hooks.use_events(move |event| {
        if let Event::Key(key_event) = event {
            if key_event.kind == KeyEventKind::Press {
                match key_event.code {
                    KeyCode::Char('1') => navigate.push("/counter"),
                    KeyCode::Char('2') => navigate.push("/input"),

                    _ => {}
                }
            }
        }
    });
    element!(
        Border(
            style:Style::default().blue(),
            height:Constraint::Length(10),
            gap:1,
            top_title:Line::from("🏠 Store 全局状态仪表盘").centered().bold(),
        ){
            $Line::from(format!("全局计数: {}", count.get()))
            $Line::from(format!("全局输入: {}", value.read().as_str()))
            $Line::from("1. 计数器页面 (Counter)")
            $Line::from("2. 文本输入页面")
        }
    )
}

#[component]
fn CounterPage(mut hooks: Hooks) -> impl Into<AnyElement<'static>> {
    let store = &COUNTER_AND_TEXT_INPUT_STORE;
    let mut count = use_stores!(store.count);
    let value = use_stores!(store.value);
    let mut navigate = hooks.use_navigate();
    hooks.use_future(async move {
        loop {
            tokio::time::sleep(std::time::Duration::from_secs(1)).await;
            count += 1;
        }
    });
    hooks.use_events(move |event| {
        if let Event::Key(key_event) = event {
            if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Esc {
                navigate.back();
            }
        }
    });
    element!(
        Border(
            style:Style::default().green(),
            height:Constraint::Length(6),
            top_title:Line::from("计数器页面 (ESC 返回)").centered(),
        ){
            $Line::from(format!("全局输入: {}", value.read().as_str()))
            $Line::styled(
                format!("Counter: {}", count.get()),
                Style::default().fg(ratatui::style::Color::Green).bold(),
            ).centered().bold().underlined()
        }
    )
}

#[component]
fn InputPage(mut hooks: Hooks) -> impl Into<AnyElement<'static>> {
    let store = &COUNTER_AND_TEXT_INPUT_STORE;
    let (mut value, count) = use_stores!(store.value, store.count);
    let mut navigate = hooks.use_navigate();
    hooks.use_events(move |event| {
        if let Event::Key(key_event) = event {
            if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Esc {
                navigate.back();
            }
        }
    });
    element!(
        Border(
            style:Style::default().cyan(),
            height:Constraint::Length(7),
            top_title:Line::from("文本输入页面 (ESC 返回)").centered(),
        ){
            $Line::from(format!("全局计数: {}", count.get()))
            TextArea(
                value: value.read().to_string(),
                is_focus:true,
                on_change: move |new_value: String| {
                    value.set(new_value);
                },
                multiline: true,
                cursor_style: Style::default().on_cyan(),
                placeholder: Some("请输入内容...".to_string()),
                placeholder_style: Style::default().cyan(),
            )
        }
    )
}

运行结果如下:

全局状态管理