前言
前面两篇我们对 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),
),
);
}
}
复制代码
核心业务分为四部分:
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,
);
}
}
复制代码
// ...
Checkbox(
value: item.selected,
onChanged: (value) {
store.dispatch(ToggleItemStateAction(item: item));
},
),
//...
复制代码
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
的状态。对于处于同一组件树的组件,都可以采用这种方式来共享状态。同时,实际开发中可以灵活运用 StoreConnector
的 converter
参数,通过这种方式可以简化界面的构建。
评论