這是我參與8月更文挑戰的第25天,活動詳情檢視:8月更文挑戰
前言
本篇繼續購物清單應用的完善,解決完離線儲存後,目前的購物清單存在兩個問題:一是沒法刪除(女朋友的購物車除外,見下圖);二是我們的中介軟體的寫法的 if...else
巢狀會隨著 Action
的增加而增加,可以進一步改進。
本篇來解決這兩個問題,話不多說,開幹!
購物車數量加減元件
在商城應用中,我們在購物車經常會用到數量控制的加減元件,如下圖所示。點選加號數量加1,點選減號數量減1。
我們也來實現一個這樣的通用元件:
- 數量顯示購物清單當前物品的數量;
- 點選加號數量加1;
- 點選減號數量減1,如果減到0就把該項刪除。
在開發元件前,我們應該先定義好元件的對外介面(包括屬性和互動方法),在購物車數量加減元件中,有如下介面需要與使用它的元件進行互動:
- 數量屬性:元件本身不承載業務,因此這個數量是由上層元件來控制的;我們定義為
count
。 - 點選加號的響應方法,我們定義為
onAdd
,該方法攜帶加1後的數量引數。 - 點選減號的響應方法,我們定義為
onSub
,該方法攜帶減1後的數量引數。 - 中間文字的寬
width
和height
,非必傳,由元件自身設定預設引數。
由於元件本身沒有自身的狀態,因此定義為 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 中的通用元件,儘可能地將構造方法定義為 const
,表示該物件是不可變的,這樣在渲染過程中效率會更高。我們以後單獨針對這個來一篇介紹。加減元件本身的實現比較簡單,這裡不貼程式碼了,需要程式碼的請看這裡:Redux狀態管理相關程式碼。
數量增減業務實現
數量增減照樣我們需要使用 Redux 的狀態管理實現,這裡新增兩個 Action
:
AddItemCountAction
:數量加1,攜帶當前的購物項ShoppingItem
。SubItemCountAction
:數量減1,攜帶當前的購物項ShoppingItem
。
然後在 Reducer
中處理增減數量邏輯:
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();
// 刪除數量等於0的元素
newItems = newItems.where((item) => item.count > 0).toList();
return newItems;
}
複製程式碼
在中介軟體中同樣需要在加減數量時進行離線儲存處理。
void shoppingListMiddleware(
Store<ShoppingListState> store, dynamic action, NextDispatcher next) async {
if (action is ReadOfflineAction) {
// 從離線儲存中讀取清單
} 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();
}
複製程式碼
就這樣,我們的加減數量的邏輯就完成了,來看看效果吧。
中介軟體程式碼優化
效果是達到預期了,但是中介軟體的程式碼有點 Low,if
裡面套了4個 Action
,而且如果 Action
一多,那 if...else
簡直沒法看。對於這種情況,Redux 中提供了一種指定 Action 的響應方式,那就是:
TypedMiddleware<T, Action>(middlewareFunction)
複製程式碼
其中 T
是對應的狀態類,Action
是對應的 Action
類,而 middlewareFunction
就是對應 Action
的處理方法。通過這種方式可以將 Action
和對應的處理中介軟體繫結起來,然後在執行中介軟體的時候會找到對繫結的 Action
對應的中介軟體執行,從而避免了if...else
的情況。然後組成一箇中介軟體陣列,這樣就不需要寫那麼多 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);
}
複製程式碼
改造完的中介軟體程式碼看似變多了,其實是因為我們的幾個 Action
的操作類似導致的,如果 Action
的業務差別比較大,程式碼量不會增加多少,但是整個程式碼的維護性增強了很多。當然,建立 Store 的 程式碼的中介軟體引數也需要改一下:
final store = Store<ShoppingListState>(
shoppingListReducer,
initialState: ShoppingListState.initial(),
middleware: shopplingListMiddleware(),
);
複製程式碼
總結
本篇完成了購物數量加減元件的開發,以及使用了TypedMiddleware
將中介軟體處理方法與對應的 Action
進行繫結避免過多的 if...else
判斷,增強了中介軟體的可維護性。使用了幾次 Redux
後,我們也會發現 Redux
的架構、業務邏輯和UI介面的職責、界限更為清晰。對於大中型專案來說,在可維護性上可能會比 Provider
更勝一籌。