Flutter 入門與實戰(六十):以購物清單為例講述 Redux 的狀態如何多元件共享

島上碼農發表於2021-08-22

這是我參與8月更文挑戰的第23天,活動詳情檢視:8月更文挑戰

前言

前面兩篇我們對 Redux 的基本使用和中介軟體有了基本的認識,這一篇我們來探討如果實現多元件共享狀態資料。和 Provider 類似,要共享資料的前提是這些元件在同一個父級元件下下級元件。

頁面結構

我們的頁面結構分為三個部分,對應的是三個元件:

  • 購物清單:支援對單個專案進行選擇或取消選擇,表示該物品是否已經完成購買;
  • 新增購物項彈窗:點選確認後新增新的物品到清單;
  • 底部購物清單統計資訊:提示當前購物清單完成進度。

介面如下圖所示,介面中的三個元件相互之間是獨立的,但都需要用到同一個狀態資料,那就是購物清單。

購物清單.jpg

介面構建的程式碼這裡就不貼了,可以到這裡下載原始碼: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 的應用示例。

螢幕錄製2021-08-22 下午3.10.12.gif

總結

本篇介紹了在頂級元件構建Store的方式實現了多元件共享 Redux 的狀態。對於處於同一元件樹的元件,都可以採用這種方式來共享狀態。同時,實際開發中可以靈活運用 StoreConnectorconverter 引數,通過這種方式可以簡化介面的構建。


我是島上碼農,微信公眾號同名,這是Flutter 入門與實戰的專欄文章,對應原始碼請看這裡:Flutter 入門與實戰專欄原始碼

??:覺得有收穫請點個贊鼓勵一下!

?:收藏文章,方便回看哦!

?:評論交流,互相進步!

相關文章