前言
本篇繼續(xù)購物清單應(yīng)用的完善,解決完離線存儲(chǔ)后,目前的購物清單存在兩個(gè)問題:一是沒法刪除(女朋友的購物車除外,見下圖);二是我們的中間件的寫法的 if...else 嵌套會(huì)隨著 Action 的增加而增加,可以進(jìn)一步改進(jìn)。
本篇來解決這兩個(gè)問題,話不多說,開干!
購物車數(shù)量加減組件
在商城應(yīng)用中,我們在購物車經(jīng)常會(huì)用到數(shù)量控制的加減組件,如下圖所示。點(diǎn)擊加號(hào)數(shù)量加1,點(diǎn)擊減號(hào)數(shù)量減1。
我們也來實(shí)現(xiàn)一個(gè)這樣的通用組件:
- 數(shù)量顯示購物清單當(dāng)前物品的數(shù)量;
- 點(diǎn)擊加號(hào)數(shù)量加1;
- 點(diǎn)擊減號(hào)數(shù)量減1,如果減到0就把該項(xiàng)刪除。
在開發(fā)組件前,我們應(yīng)該先定義好組件的對外接口(包括屬性和交互方法),在購物車數(shù)量加減組件中,有如下接口需要與使用它的組件進(jìn)行交互:
- 數(shù)量屬性:組件本身不承載業(yè)務(wù),因此這個(gè)數(shù)量是由上層組件來控制的;我們定義為
count。 - 點(diǎn)擊加號(hào)的響應(yīng)方法,我們定義為
onAdd,該方法攜帶加1后的數(shù)量參數(shù)。 - 點(diǎn)擊減號(hào)的響應(yīng)方法,我們定義為
onSub,該方法攜帶減1后的數(shù)量參數(shù)。 - 中間文本的寬
width和height,非必傳,由組件自身設(shè)定默認(rèn)參數(shù)。
由于組件本身沒有自身的狀態(tài),因此定義為 StatelessWidget,如下所示:
class CartNumber extends StatelessWidget {
final ValueChanged<int> onSub;
final ValueChanged<int> onAdd;
final int count;
final double width;
final double height;
const CartNumber({
Key? key,
required this.count,
required this.onAdd,
required this.onSub,
this.width = 40,
this.height = 40,
}) : super(key: key);
這里順帶講一下,對于Flutter 中的通用組件,盡可能地將構(gòu)造方法定義為 const,表示該對象是不可變的,這樣在渲染過程中效率會(huì)更高。我們以后單獨(dú)針對這個(gè)來一篇介紹。加減組件本身的實(shí)現(xiàn)比較簡單,這里不貼代碼了,需要代碼的請看這里:Redux狀態(tài)管理相關(guān)代碼。
數(shù)量增減業(yè)務(wù)實(shí)現(xiàn)
數(shù)量增減照樣我們需要使用 Redux 的狀態(tài)管理實(shí)現(xiàn),這里新增兩個(gè) Action:
-
AddItemCountAction:數(shù)量加1,攜帶當(dāng)前的購物項(xiàng)ShoppingItem。 -
SubItemCountAction:數(shù)量減1,攜帶當(dāng)前的購物項(xiàng)ShoppingItem。
然后在 Reducer 中處理增減數(shù)量邏輯:
ShoppingListState shoppingListReducer(ShoppingListState state, action) {
// ...
if (action is AddItemCountAction) {
var newItems = addItemCountActionHandler(state.shoppingItems, action.item);
return ShoppingListState(shoppingItems: newItems);
}
if (action is SubItemCountAction) {
var newItems = subItemCountActionHandler(state.shoppingItems, action.item);
return ShoppingListState(shoppingItems: newItems);
}
return state;
}
List<ShoppingItem> addItemCountActionHandler(
List<ShoppingItem> oldItems, ShoppingItem itemToHandle) {
List<ShoppingItem> newItems = oldItems.map((item) {
if (item == itemToHandle) {
return ShoppingItem(
name: item.name, selected: item.selected, count: item.count + 1);
} else {
return item;
}
}).toList();
return newItems;
}
List<ShoppingItem> subItemCountActionHandler(
List<ShoppingItem> oldItems, ShoppingItem itemToHandle) {
List<ShoppingItem> newItems = oldItems.map((item) {
if (item == itemToHandle) {
return ShoppingItem(
name: item.name, selected: item.selected, count: item.count - 1);
} else {
return item;
}
}).toList();
// 刪除數(shù)量等于0的元素
newItems = newItems.where((item) => item.count > 0).toList();
return newItems;
}
在中間件中同樣需要在加減數(shù)量時(shí)進(jìn)行離線存儲(chǔ)處理。
void shoppingListMiddleware(
Store<ShoppingListState> store, dynamic action, NextDispatcher next) async {
if (action is ReadOfflineAction) {
// 從離線存儲(chǔ)中讀取清單
} else if (action is AddItemAction ||
action is ToggleItemStateAction ||
action is AddItemCountAction ||
action is SubItemCountAction) {
List<Map<String, String>> listToSave =
_prepareForSave(store.state.shoppingItems, action);
SharedPreferences.getInstance().then((prefs) =>
prefs.setString(SHOPPLINT_LIST_KEY, json.encode(listToSave)));
} else if (action is AddItemCountAction) {
} else {
// ReadOfflineSuccessAction:無操作
}
next(action);
}
List<Map<String, String>> _prepareForSave(
List<ShoppingItem> oldItems, dynamic action) {
List<ShoppingItem> newItems = [];
// ...
if (action is AddItemCountAction) {
newItems = addItemCountActionHandler(oldItems, action.item);
}
if (action is SubItemCountAction) {
newItems = subItemCountActionHandler(oldItems, action.item);
}
return newItems.map((item) => item.toJson()).toList();
}
就這樣,我們的加減數(shù)量的邏輯就完成了,來看看效果吧。
中間件代碼優(yōu)化
效果是達(dá)到預(yù)期了,但是中間件的代碼有點(diǎn) Low,if 里面套了4個(gè) Action,而且如果 Action 一多,那 if...else 簡直沒法看。對于這種情況,Redux 中提供了一種指定 Action 的響應(yīng)方式,那就是:
TypedMiddleware<T, Action>(middlewareFunction)
其中 T 是對應(yīng)的狀態(tài)類,Action 是對應(yīng)的 Action 類,而 middlewareFunction 就是對應(yīng) Action 的處理方法。通過這種方式可以將 Action 和對應(yīng)的處理中間件綁定起來,然后在執(zhí)行中間件的時(shí)候會(huì)找到對綁定的 Action 對應(yīng)的中間件執(zhí)行,從而避免了if...else 的情況。然后組成一個(gè)中間件數(shù)組,這樣就不需要寫那么多 if...else 了。改造完的中間件代碼如下:
List<Middleware<ShoppingListState>> shopplingListMiddleware() => [
TypedMiddleware<ShoppingListState, ReadOfflineAction>(
_readOfflineActionMiddleware),
TypedMiddleware<ShoppingListState, AddItemAction>(
_addItemActionMiddleware),
TypedMiddleware<ShoppingListState, ToggleItemStateAction>(
_toggleItemActionMiddleware),
TypedMiddleware<ShoppingListState, AddItemCountAction>(
_addItemCountActionMiddleware),
TypedMiddleware<ShoppingListState, SubItemCountAction>(
_subItemCountActionMiddleware),
];
void _readOfflineActionMiddleware(Store<ShoppingListState> store,
ReadOfflineAction action, NextDispatcher next) {
SharedPreferences.getInstance().then((prefs) {
dynamic offlineList = prefs.get(SHOPPLINT_LIST_KEY);
if (offlineList != null && offlineList is String) {
store.dispatch(
ReadOfflineSuccessAction(offlineList: json.decode(offlineList)));
}
});
next(action);
}
void _addItemActionMiddleware(
Store<ShoppingListState> store, AddItemAction action, NextDispatcher next) {
List<Map<String, String>> listToSave =
_prepareForSave(store.state.shoppingItems, action);
SharedPreferences.getInstance().then(
(prefs) => prefs.setString(SHOPPLINT_LIST_KEY, json.encode(listToSave)));
next(action);
}
void _toggleItemActionMiddleware(Store<ShoppingListState> store,
ToggleItemStateAction action, NextDispatcher next) {
List<Map<String, String>> listToSave =
_prepareForSave(store.state.shoppingItems, action);
SharedPreferences.getInstance().then(
(prefs) => prefs.setString(SHOPPLINT_LIST_KEY, json.encode(listToSave)));
next(action);
}
void _addItemCountActionMiddleware(Store<ShoppingListState> store,
AddItemCountAction action, NextDispatcher next) {
List<Map<String, String>> listToSave =
_prepareForSave(store.state.shoppingItems, action);
SharedPreferences.getInstance().then(
(prefs) => prefs.setString(SHOPPLINT_LIST_KEY, json.encode(listToSave)));
next(action);
}
void _subItemCountActionMiddleware(Store<ShoppingListState> store,
SubItemCountAction action, NextDispatcher next) {
List<Map<String, String>> listToSave =
_prepareForSave(store.state.shoppingItems, action);
SharedPreferences.getInstance().then(
(prefs) => prefs.setString(SHOPPLINT_LIST_KEY, json.encode(listToSave)));
next(action);
}
改造完的中間件代碼看似變多了,其實(shí)是因?yàn)槲覀兊膸讉€(gè) Action的操作類似導(dǎo)致的,如果 Action 的業(yè)務(wù)差別比較大,代碼量不會(huì)增加多少,但是整個(gè)代碼的維護(hù)性增強(qiáng)了很多。當(dāng)然,創(chuàng)建 Store 的 代碼的中間件參數(shù)也需要改一下:
final store = Store<ShoppingListState>(
shoppingListReducer,
initialState: ShoppingListState.initial(),
middleware: shopplingListMiddleware(),
);
總結(jié)
本篇完成了購物數(shù)量加減組件的開發(fā),以及使用了TypedMiddleware將中間件處理方法與對應(yīng)的 Action 進(jìn)行綁定避免過多的 if...else判斷,增強(qiáng)了中間件的可維護(hù)性。使用了幾次 Redux 后,我們也會(huì)發(fā)現(xiàn) Redux 的架構(gòu)、業(yè)務(wù)邏輯和UI界面的職責(zé)、界限更為清晰。對于大中型項(xiàng)目來說,在可維護(hù)性上可能會(huì)比 Provider更勝一籌。