写点什么

以购物清单为例讲述 Redux 的状态如何在 Flutter 多个组件间共享

作者:岛上码农
  • 2022 年 6 月 05 日
  • 本文字数:2312 字

    阅读完需:约 8 分钟

以购物清单为例讲述 Redux 的状态如何在 Flutter 多个组件间共享

前言

前面两篇我们对 Redux 的基本使用和中间件有了基本的认识,这一篇我们来探讨如果实现多组件共享状态数据。和 Provider 类似,要共享数据的前提是这些组件在同一个父级组件下下级组件。

页面结构

我们的页面结构分为三个部分,对应的是三个组件:


  • 购物清单:支持对单个项目进行选择或取消选择,表示该物品是否已经完成购买;

  • 新增购物项弹窗:点击确认后添加新的物品到清单;

  • 底部购物清单统计信息:提示当前购物清单完成进度。


界面如下图所示,界面中的三个组件相互之间是独立的,但都需要用到同一个状态数据,那就是购物清单。

界面构建的代码这里就不贴了,可以到这里下载源码:Redux 状态管理源码。

组件间共享状态

上面的三个组件其实是不相关的,那么就需要将状态定义在三个组件的共同的上级组件上。这里有个特殊的组件是添加购物项的对话框弹窗,调用方法是:


void _openAddItemDialog(BuildContext context) {  showDialog(context: context, builder: (context) => AddItemDialog());}
复制代码


这里的 showDialog 方法其实是弹出了一个新的页面,这就导致了这个弹出的对话框除了 MaterialApp 外,不是其他组件的子组件。因此,状态的定义就需要提到顶级,也就是放置在 MaterialApp的上级,如下所示。这样整个应用的组件都可以共享到这个状态管理了。


class MainApp extends StatelessWidget {  MainApp({Key? key}) : super(key: key);  final store = Store<ShoppingListState>(    shoppingListReducer,    initialState: ShoppingListState.initial(),  );  @override  Widget build(BuildContext context) {    return StoreProvider(      store: store,      child: MaterialApp(        title: 'Redux Counter Demo',        theme: ThemeData(          primarySwatch: Colors.blue,        ),        home: ShoppingListHome(),        builder: EasyLoading.init(),      ),    );  }}
复制代码

核心业务

实际业务的组件为 ShoppingListHome:


class ShoppingListHome extends StatelessWidget {  const ShoppingListHome({Key? key}) : super(key: key);
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('购物清单'), ), body: StoreConnector<ShoppingListState, List<Widget>>( converter: (store) => _ShoppListViewModel.build(store), builder: (context, items) => ListView.builder( itemBuilder: (context, index) => items[index], itemCount: items.length, ), ), bottomSheet: _BottomStatisticBar(), floatingActionButton: FloatingActionButton( child: Icon(Icons.add), onPressed: () => _openAddItemDialog(context), ), ); }}
复制代码


核心业务分为四部分:


  • 清单显示:这里使用了 StoreConnector 直接将状态的清单列表通过 converter转换为要显示的组件列表,这样 ListView.builder 可以直接使用。

  • 底部统计:底部统计读取状态列表中的已选中的个数,也是 通过 converter 进行转换。


class _BottomStatisticBar extends StatelessWidget {  const _BottomStatisticBar({Key? key}) : super(key: key);
@override Widget build(BuildContext context) { return StoreConnector<ShoppingListState, int>( builder: (context, selectedCount) => Container( height: 60, width: double.infinity, color: Colors.grey, padding: EdgeInsets.all(10), child: Text('已完成$selectedCount项'), ), converter: (store) => store.state.shoppingItems.where((item) => item.selected).length, ); }}
复制代码


  • 勾选框选中或取消选择:此时只需要通过 store 发起状态转变的 ToggleItemStateAction


// ...Checkbox(  value: item.selected,  onChanged: (value) {    store.dispatch(ToggleItemStateAction(item: item));  },),//...
复制代码


  • 添加新购物项:这里我们通过 converter 将状态转换为一个回调函数,以便对话框点击添加按钮后使用该回调函数发起回调。


class AddItemDialog extends StatelessWidget {  @override  Widget build(BuildContext context) {    return StoreConnector<ShoppingListState, OnItemAddedCallback>(      converter: (store) {        return (itemName) => store.dispatch(              AddItemAction(                  item: ShoppingItem(                name: itemName,                selected: false,              )),            );      },      builder: (context, callback) {        return _AddItemDialogWidget(callback);      },    );  }}
复制代码


从这几个业务可以看到,converter 的方式十分灵活,可以根据我们界面需要哪些元素,将状态进行转换得到视图模型,从而可以简化我们的视图模型的构建。

运行结果

运行结果如下图所示,这里我们会发现一个问题,那就是如果两个购物项名称相同,会导致复选框的选中出现问题,而实际上,对于添加相同的选项时,我们可以增加数量。下一篇我们针对这个应用进行进一步完善,例如数量显示问题以及离线存储,实现一个相对完善的 Redux 的应用示例。


总结

本篇介绍了在顶级组件构建Store的方式实现了多组件共享 Redux 的状态。对于处于同一组件树的组件,都可以采用这种方式来共享状态。同时,实际开发中可以灵活运用 StoreConnectorconverter 参数,通过这种方式可以简化界面的构建。


发布于: 刚刚阅读数: 3
用户头像

岛上码农

关注

用代码连接孤岛,公众号@岛上码农 2022.03.03 加入

从南漂到北,从北漂到南的业余码农

评论

发布
暂无评论
以购物清单为例讲述 Redux 的状态如何在 Flutter 多个组件间共享_flutter_岛上码农_InfoQ写作社区