這是我參與8月更文挑戰的第23天,活動詳情檢視:8月更文挑戰
前言
前面兩篇我們對 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
的狀態。對於處於同一元件樹的元件,都可以採用這種方式來共享狀態。同時,實際開發中可以靈活運用 StoreConnector
的 converter
引數,通過這種方式可以簡化介面的構建。
我是島上碼農,微信公眾號同名,這是Flutter 入門與實戰的專欄文章,對應原始碼請看這裡:Flutter 入門與實戰專欄原始碼。
??:覺得有收穫請點個贊鼓勵一下!
?:收藏文章,方便回看哦!
?:評論交流,互相進步!