【LiveStates 05】实战指南:手把手带你用 LiveStates 构建高性能生产级页面

15 阅读4分钟

【LiveStates 05】实战指南:手把手带你用 LiveStates 构建高性能生产级页面

【LiveStates 01】别再手动 watch 了:开启 Flutter “自动追踪” DX 革命

【LiveStates 02】Zones 不止于异常捕获:揭秘 LiveStates 自动追踪黑科技

【LiveStates 03】拒绝无效重绘:利用 LiveCompute 实现手术刀级 UI 刷新

【LiveStates 04】不仅是状态管理:解锁 Recoverable/Refreshable 工业级特性

看了前几篇原理,你可能觉得 live_states 很玄学。其实,它的核心开发心智可以总结为一句话:“数据是唯一的真理,UI 只是数据的投影。”

今天,我们就按这个心智模型,走一遍标准的开发流程。我们将从零开始构建一个“带状态恢复和异步过滤”的生产级页面,让你彻底掌握它的底层开发逻辑。


第一步:DNA 建模——定义你的状态(State)

不要急着写 UI,先写 ViewModel。在 live_states 里,ViewModel 是独立于 UI 存在的“逻辑实体”。

原则:凡是会变的、需要计算的,全部定义为 LiveDataLiveCompute

class ProductListVM extends LiveViewModel<ProductListPage> with Recoverable {
  // 1. 定义存储 key(用于状态恢复)
  @override
  String get storageKey => 'product_list_page_cache';

  // 2. 原始状态:搜索词和数据列表
  late final searchKey = LiveData<String>('', owner);
  late final products = LiveData<List<Product>>([], owner);

  // 3. 派生状态:利用 LiveCompute 实现自动过滤
  // 它会自动追踪 searchKey 和 products,只有结果变了才会通知 UI
  late final filteredList = LiveCompute<List<Product>>(owner, () {
    final key = searchKey.value.toLowerCase();
    if (key.isEmpty) return products.value;
    return products.value.where((p) => p.name.contains(key)).toList();
  });

  // 状态恢复逻辑
  @override
  Map<String, dynamic>? storage() => {'q': searchKey.value};
  @override
  void recover(Map<String, dynamic>? s) => searchKey.value = s?['q'] ?? '';
}

第二步:神经中枢——编写业务逻辑(Actions)

在 ViewModel 里,你可以放心地处理异步请求、定时器或复杂的计算。

原则:禁止在 ViewModel 里持有任何 Widget 对象。

  // 初始化数据
  @override
  void init() {
    super.init();
    loadProducts();
  }

  Future<void> loadProducts() async {
    // 模拟网络请求
    await Future.delayed(const Duration(seconds: 1));
    products.value = [Product('Apple'), Product('Banana'), Product('Cherry')];
  }

  void onSearch(String value) {
    searchKey.value = value;
  }

第三步:投影——构建 UI 与局部刷新(View)

到了 View 层,你的任务只有一个:把数据精准地“贴”在屏幕上。

核心技巧:外科手术式局部刷新

不要直接在 build 方法里读 .value(除非你真的想整页刷新)。利用 LiveScope 来缩小刷新的范围。

class ProductListPage extends LiveWidget {
  @override
  ProductListVM createViewModel() => ProductListVM();

  @override
  Widget build(BuildContext context, ProductListVM viewModel) {
    return Scaffold(
      appBar: AppBar(title: const Text('LiveStates Demo')),
      body: Column(
        children: [
          // 搜索框:它读取状态,但它自己不需要跟随状态刷新(因为它是输入源)
          TextField(
            onChanged: viewModel.onSearch,
            controller: TextEditingController(text: viewModel.searchKey.value)
              ..selection = TextSelection.collapsed(offset: viewModel.searchKey.value.length),
          ),
          
          Expanded(
            // 局部刷新域:只有列表部分会根据过滤结果刷新
            child: LiveScope.free(
              builder: (context, _) {
                final list = viewModel.filteredList.value;
                if (list.isEmpty) return const CircularProgressIndicator();
                return ListView.builder(
                  itemCount: list.length,
                  itemBuilder: (c, i) => ListTile(title: Text(list[i].name)),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

第四步:副作用(Side Effects)——如何优雅地弹窗或跳转?

这是很多状态管理框架的软肋:数据变了,但我只想弹一个 SnackBar,而不是刷新 UI。

live_states 里,我们推荐 “侦听器模式”。你可以利用 LiveDatalisten 方法,或者在 LiveScope 里处理瞬态逻辑。

// 在 ViewModel 中定义一个事件流
late final toastMessage = LiveData<String?>(null, owner);

// 在 View 中,利用一个不返回组件的 LiveScope 来“监听”并执行副作用
LiveScope.free(
  builder: (context, _) {
    final msg = viewModel.toastMessage.value;
    if (msg != null) {
      // 这里的逻辑只在 msg 变化时运行一次
      WidgetsBinding.instance.addPostFrameCallback((_) {
        ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg)));
        viewModel.toastMessage.onlyValue = null; // 处理完重置
      });
    }
    return const SizedBox.shrink(); // 不占据空间
  }
)

总结:live_states 的“禅意”三段论
  1. 定义数据 (DNA):想清楚哪些是原始数据,哪些是计算出来的。
  2. 封装逻辑 (Brain):在 VM 里处理业务,完全不需要考虑 context 和 UI 刷新。
  3. 精准映射 (Mapping):在 UI 层用 LiveScope 像贴补丁一样,把数据映射到屏幕。

避坑指南:

  • 别在 LiveScope 外面读 .value:除非你确定要整页刷新。
  • 多用 onlyValue:如果你只是想在方法里用一下当前值,不需要监听,记得用 onlyValue,省去不必要的订阅开销。
  • ViewModel 是单向的:它可以被 View 调用,但它永远不知道 View 到底长什么样。

写在最后: live_states 不是为了让你写更多代码,而是为了让你在写代码时更“笃定”。你定义了数据,你触碰了数据,UI 就会精准地响应。这种**“所见即所得”**的确定性,才是它能带给你的最大自由。