Flutter 入門與實戰(六十九):MobX 如何實現多元件的狀態共享?

島上碼農發表於2021-09-01

前言

前面3篇我們介紹了 MobX 的概念和使用, MobX 使用起來確實很簡單,也很高效。但是, MobX 也有一個缺陷,那就是如果狀態資料要共享的時候,我們並不能像 Provider 或 Redux 那樣將狀態物件提升到共同上級元件來完成。還是以點贊和收藏按鈕的介面為例(Redux 的可以參考:Flutter 入門與實戰(六十四):這篇很長,為了效能,你忍一下 —— 從原始碼看 flutter_redux精準重新整理)。兩個按鈕是獨立的元件,點選時都會增加對應的數量,兩個元件共享一個狀態物件。

image.png

MobX 共享方式一:逐級傳遞

由於 MobX 本身不支援上級元件狀態可以被下級元件共享,因此我們想到的最簡單的方式就是將狀態物件傳遞給下級元件。

class DynamicDetailWrapper extends StatelessWidget {
  final store = ShareStore();
  DynamicDetailWrapper({Key? key}) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    //...
    		children: [
          _PraiseButton(store: store),
          _FavorButton(store: store),
        ],
  }
}

class _FavorButton extends StatelessWidget {
  final ShareStore store;
  const _FavorButton({Key? key, required this.store}) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    print('FavorButton');
    return Container(
      alignment: Alignment.center,
      color: Colors.blue,
      child: TextButton(
        onPressed: () {
          store.increamentFavor();
        },
        child: Observer(
          builder: (context) => Text(
            '收藏 ${store.favorCount}',
            style: TextStyle(color: Colors.white),
          ),
        ),
        style: ButtonStyle(
            minimumSize: MaterialStateProperty.resolveWith(
                (states) => Size((MediaQuery.of(context).size.width / 2), 60))),
      ),
    );
  }
}

// 省略點贊按鈕程式碼
複製程式碼

執行一下,效果也能達到,但是存在的問題就是如果一個狀態是全域性的,意味著需要將這個狀態物件傳遞給每一個需要該狀態的子元件,這個方式太醜陋了!那有沒有別的方法呢?

MobX 狀態共享方式二:借用 Provider

既然 MobX 不支援在上層元件定義狀態物件,通過context 來給下級元件共享,那可以給他拉一個搭檔來,顯然 Provider 是一個不錯的搭檔。我們可以將 MobX 的共享狀態物件由 Provider 來定義,然後共享給下級元件。

P.S. 發現 Provider 都已經升級到6.0.0版本了,正好試試新版本怎麼樣?

 class DynamicDetailProvider extends StatelessWidget {
   DynamicDetailProvider({Key? key}) : super(key: key);
   
   @override
   Widget build(BuildContext context) {
     //省略其他程式碼
        child: Provider(
          child: Row(
            mainAxisSize: MainAxisSize.min,
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: [
              _PraiseButton(),
              _FavorButton(),
            ],
          ),
          create: (context) => ShareStore(),
        ),
   }
 }

class _FavorButton extends StatelessWidget {
  const _FavorButton({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print('FavorButton');
    return Container(
      alignment: Alignment.center,
      color: Colors.blue,
      child: TextButton(
        onPressed: () {
          context.read<ShareStore>().increamentFavor();
        },
        child: Observer(
          builder: (context) => Text(
            '收藏 ${context.read<ShareStore>().favorCount}',
            style: TextStyle(color: Colors.white),
          ),
        ),
        style: ButtonStyle(
            minimumSize: MaterialStateProperty.resolveWith(
                (states) => Size((MediaQuery.of(context).size.width / 2), 60))),
      ),
    );
  }
}
複製程式碼

這種方式相比之前的方式就優雅多了,只是在需要共享使用 MobX 狀態物件的元件的上級使用 Provider 包裹它們,然後使用 context.read()方法獲取狀態物件就可以了。那還有沒有更好的方式?

MobX 狀態共享方式三:使用 GetIt 容器

我們在Flutter 入門與實戰(二十七):使用 GetIt 同步不同頁面間資料曾經介紹過GetIt 容器。事實上,GetIt 容器可以儲存我們的狀態物件,在需要使用狀態物件的時候,從容器中取出來就可以了。

class DynamicDetailGetIt extends StatelessWidget {
  DynamicDetailGetIt({Key? key}) : super(key: key) {
    GetIt.I.registerSingleton<ShareStore>(ShareStore());
  }
  
  //省略 build 方法
}

class _FavorButton extends StatelessWidget {
  const _FavorButton({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print('FavorButton');
    return Container(
      alignment: Alignment.center,
      color: Colors.blue,
      child: TextButton(
        onPressed: () {
          GetIt.I.get<ShareStore>().increamentFavor();
        },
        child: Observer(
          builder: (context) => Text(
            '收藏 ${GetIt.I.get<ShareStore>().favorCount}',
            style: TextStyle(color: Colors.white),
          ),
        ),
        style: ButtonStyle(
            minimumSize: MaterialStateProperty.resolveWith(
                (states) => Size((MediaQuery.of(context).size.width / 2), 60))),
      ),
    );
  }
}
複製程式碼

可以看到,這種方式相對上面的兩種方式更為簡潔:

  • 註冊單例狀態物件到 GetIt容器
  • 在需要使用狀態物件的地方從GetIt容器取出即可,開箱即用!
  • 無需依賴 context,可以避免 context 過於臃腫影響效能。

總結

示例原始碼請點選此處:MobX 狀態管理原始碼。本篇介紹了3種可用於MobX 狀態共享的方法,由於 MobX 無法直接利用上級元件給下級元件共享,因此需要藉助其他方式實現狀態共享。對比可以發現嗎,使用GetIt 容器的方式更為簡潔、優雅,元件間的耦合度也最低。因此,推薦在使用 MobX 進行狀態管理代理的場合使用這種方式。


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

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

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

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

相關文章