# 状态管理

> CrossFly 的状态模式 —— 没有响应式绑定，手动 ViewModel + onUI + repaint。

Source: https://crossfly.dev/zh/docs/data-and-services/state-management — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# 状态管理

如果你来自 React/Flutter/Compose，请做好准备迎接最大的差异：**没有响应式数据绑定**。没有会自动重绘的
`useState`/`Observable`/`setState()`。这里的模型是**命令式 + 显式失效** —— 其中的“为什么”在
[状态与数据流](/docs/concepts/state-and-data-flow)中说明。

## 该模式
状态存在于你自己对象（一个 ViewModel/服务）的**普通字段**中。当它变化时，**你更新
widget**并调用 `repaint()` —— 如果来自其他线程，则放在 `Component.onUI(...)` 内。
```java
class HomeVM {
   List<Cliente> clientes = new ArrayList<>();
}

public class TelaHome extends Window {
   private final HomeVM vm = new HomeVM();
   private ListView<Cliente> lista;

   @Override public void onShow() throws Exception {
      super.onShow();
      setLayout(new VerticalLayout());
      add(LAY_FILL, lista = new ListView<>());
      carregar();
   }

   private void carregar() {
      new Thread(() -> {
         List<Cliente> dados = repositorio.buscarTudo();   // 繁重工作，后台 thread
         Component.onUI(() -> {                             // 返回 paint thread
            vm.clientes = dados;
            lista.setItems(dados);                          // 更新 widget
            Component.repaint();                            // 请求新的一帧
         });
      }).start();
   }
}
```

## 临时状态 vs. 应用状态
| 类型 | 存在于何处 | 能否在导航后存活？ |
|---|---|---|
| **屏幕临时状态** | `Window` 的字段 | 否 —— 离开屏幕即消失 |
| **应用状态** | 一个共享的单例/服务 | 是 —— 在各屏幕间持久保留 |

按作用域选择：搜索的过滤条件是临时的；已登录的用户是应用级的。

## 最佳实践
- **每个屏幕一个 ViewModel** 集中状态和逻辑；`Window` 只负责连接 UI ↔ VM。
- 屏幕间共享的状态 → **服务/单例**，而非散落各处的静态字段。
- 从后台线程操作 widget 之前，务必先用 `onUI(...)` 回到 UI。

## 常见错误
- 期望修改模型就能自动更新 UI —— **不会更新**；请调用 `setX(...)` + `repaint()`。
- 把应用状态存在 `Window` 的字段里（导航时会丢失）。

## 另见
- 概念：[状态与数据流](/docs/concepts/state-and-data-flow) · [绘制线程](/docs/concepts/the-paint-thread)
- [使用 SQLite 持久化](/docs/data-and-services/storage-sqlite) · [后台工作](/docs/data-and-services/background-work)
- 应用架构：[best-practices](/docs/best-practices)
