【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 存在的"逻辑实体"。
原则 :凡是会变的、需要计算的,全部定义为 LiveData 或 LiveCompute。
dart
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 对象。
dart
// 初始化数据
@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 来缩小刷新的范围。
dart
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 里,我们推荐 "侦听器模式" 。你可以利用 LiveData 的 listen 方法,或者在 LiveScope 里处理瞬态逻辑。
dart
// 在 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 的"禅意"三段论
- 定义数据 (DNA):想清楚哪些是原始数据,哪些是计算出来的。
- 封装逻辑 (Brain):在 VM 里处理业务,完全不需要考虑 context 和 UI 刷新。
- 精准映射 (Mapping) :在 UI 层用
LiveScope像贴补丁一样,把数据映射到屏幕。
避坑指南:
- 别在 LiveScope 外面读
.value:除非你确定要整页刷新。 - 多用
onlyValue:如果你只是想在方法里用一下当前值,不需要监听,记得用onlyValue,省去不必要的订阅开销。 - ViewModel 是单向的:它可以被 View 调用,但它永远不知道 View 到底长什么样。
写在最后: live_states 不是为了让你写更多代码,而是为了让你在写代码时更"笃定"。你定义了数据,你触碰了数据,UI 就会精准地响应。这种**"所见即所得"**的确定性,才是它能带给你的最大自由。