跳转到内容

Atom 全局状态

Atom 适合保存进程级共享状态:主题、登录用户、跨页面缓存、全局选择结果。这个例子把“已提交的状态”放进 Atom,把“输入中的草稿”留在页面局部 use_state,避免全局状态被每个按键污染。

atom state demo

这个录屏由 docs/tapes/atom-state.tape 生成。示例先进入 focus editor 输入 write atom docs,提交后 dashboard 的订阅组件立即显示新值;再进入 score editor 调整分数,回到 dashboard 时另一个组件也读到同一个 Atom。

cargo run --example atom_state

在 dashboard 按 1 编辑 focus,按 2 编辑 score,按 q 退出。focus editor 里输入文本后按 Enter 提交,score editor 里按 +/-/r 修改分数,按 Esc 返回。

Atom 保存已提交的共享状态;输入草稿仍然留在编辑页自己的 use_state。

Atom 是模块级声明,不需要宏:

static FOCUS: Atom<String> = Atom::new(|| "Review runtime".to_string());
static SCORE: Atom<i32> = Atom::new(|| 2);
static LAST_EVENT: Atom<String> = Atom::new(|| "dashboard initialized".to_string());

Atom::new 接收初始化函数。真实状态会在第一次读取或 use_atom 时惰性创建,之后跟随进程生命周期存在。

组件内用 use_atom 订阅:

#[component]
fn FocusCard(mut hooks: Hooks) -> impl Into<AnyElement<'static>> {
    let focus = hooks.use_atom(&FOCUS);

    element!(
        Border(top_title: Line::from(" shared focus ").centered()) {
            Text(text: Line::from(focus.read().to_string()).centered())
        }
    )
}

use_atom(&FOCUS) 返回 AtomState<T>。它和 State<T> 一样是可复制的响应式句柄:读用 get()read(),写用 set()write()+= 这类运算符。

FocusCardScoreCardEventCard 是三个独立组件。它们各自订阅自己需要的 Atom;写入 SCORE 不需要把 focus 文本也放进同一个大结构里。

focus editor 里有一个单行输入框。输入过程中的字符是局部状态:

let draft = hooks.use_state(tui_input::Input::default);
let mut focus = hooks.use_atom(&FOCUS);

// typing
draft.write().handle_event(&event);

// Enter
let value = draft.read().value().trim().to_string();
focus.set(value);

这条边界很重要。草稿属于当前编辑页,页面卸载后就该消失;提交后的 focus 才属于整个应用。把每个按键都写进 Atom 会让无关组件频繁醒来,也会让“取消编辑”变得别扭。

示例用 RouterProvider 在三个页面之间切换,但 Router 自己的 history 没有放进 Atom:

let routes = routes! {
    "/" => Dashboard,
    "/focus" => FocusEditor,
    "/score" => ScoreEditor,
};

Router 是 Provider 作用域状态,适合留在 RouterProvider 内部。Atom 是进程级状态,适合多个页面都要读写的业务数据。需要给一次导航携带临时 payload 时,用 push_with_state / replace_with_state;不要用全局 Atom 模拟路由状态。

优先把状态放在最小作用域:

状态放哪里
输入框草稿use_state
列表游标组件内部 state 或外部 State<ListState>
当前登录用户Atom<User>
跨页面缓存Atom<Cache>
Router historyRouterProvider 内部 state

下一步看 输入互斥,理解共享状态之外,终端键盘事件如何按 layer 路由。