Flutter 入门与实战(六十九):MobX 如何实现多组件的状态共享?

1,304 阅读3分钟

前言

前面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 入门与实战专栏源码

👍🏻:觉得有收获请点个赞鼓励一下!

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

💬:评论交流,互相进步!