MoLc —— Flutter状态管理组件

1,234 阅读5分钟

MoLc是我基于Provider封装的一个状态管理的库。克服了一些Provider的不足,吸取了GetXRiverPod的一些特性,实现了数据逻辑分离、状态共享、逻辑复用、精准刷新、颗粒刷新等特性。

先介绍使用MoLc你能得到什么:

  • Model、Logic分离
  • 状态共享
    • 子context访问上层Model数据
    • 任意位置访问全局Model
    • 任意位置访问存在于context树中的Model
  • 逻辑复用
    • 子context调用上层Logic
    • 任意位置调用存在于context树中的Logic
  • 精准刷新
    • 全局Model的局部刷新
    • Model中仅UI属性改变时刷新

MoLc是Model、Logic的组合,旨在分离数据模型和业务逻辑。我是Android出身,MVP模式在当年还算火热,所以刚接触Flutter就做了这样一个封装,也就是MoLc的初版。个人觉得MoLc适合大部分中大型项目,欢迎大佬前来指导不足之处。下面来讲讲目前MoLc实现了什么,

使用MoLc之前要了解ModelLogic

Model

Model是存放Widget状态的类,直接使用

class TestModel extends Model {}

如果你想要在Model中使用context,比如需要通过context获取文本S.of(context),那么使用子类WidgetModel

class Test1Model extends WidgetModel {
    String testText get => S.of(context).test; //可引用context
}

在子context中获取上层contextModelcontext.read<T>(),这是Provider提供的,大家应该很熟悉。

  • 刷新
test1Model.refresh();
  • Selector 不同于Provider的Selector,MoLc的Selector是用于精细化刷新。

混入SelectorMixin<T>

class TestModel extends Model with SelectorMixin<Tuple3>{}

实现 T selectWith();方法, 返回值的是否改变决定了Model是否刷新。

@override
Tuple3<int, String, bool> selectWith() {
  return Tuple3(this.field1, this.field2, this.field3);
}

Logic

Logic是存放业务逻辑的类,使用

class TestLogic extends Logic {}

同样可以在子context中获取上层context的Logic,也是用context.read<T>()Logic也提供了子类WidgetLogic用于获取context
另外,Logic还提供了一个子类MoLogic<T>用于获取与之绑定的Model。此时logic和model是强关联的关系,可能会影响logic的复用。所以虽然model可以直接在logic获取到,但不建议直接在普通业务逻辑函数中使用model,不然数据模型和业务逻辑分离的初衷就被破坏了。仅建议在dispose中使用model,用来执行一些页面或者小组件销毁时需要的逻辑。

MoLcWidget

MoLc的核心组件是 ModelWidgetLogicWidgetMoLcWidget,前两个适合只有Model或者Logic的Widget,用的比较少,重点讲一下MoLcWidget,下面是MoLcWidget的写法:

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

  @override
  Widget build(BuildContext context) {
    return MoLcWidget<_ExampleModel, _ExampleLogic>(
      modelCreate: (_) => _ExampleModel(),
      logicCreate: (_) => _ExampleLogic(),
      init: (_, model, logic) => logic.init(model),
      builder: (context, model, logic, __) => Container(),
    );
  }
}

class _ExampleModel extends Model {}

class _ExampleLogic extends Logic {
  void init(_ExampleModel model) {}
}

如同Provider一样,还是有些样板代码,不过可以使用IDE的Live Template,也还算方便。MoLc把model和logic从Widget里分离出去,你可以在model里定义Widget的状态,可以在logic写Widget逻辑代码,然后在MoLcWidget里面调用。看到这里你可能觉得没什么,只是对Provider的一层封装而已,没错,这就是他的第一个版本的基本功能。

TopProvider

接下来说数据共享,很多使用Provider的人可能用的最多的反而是处于APP之上的顶级Model,像这样:

MultiProvider(
      providers: ...,     
      child: MaterialApp(
        ...
      ),
    ),

这是由于InheritedWidget的特性,context节点位置要位于MaterialApp 之上才能让App中的所有路由页面访问到,才能实现跨页面传输数据。这里MoLc封装了一层TopProvider,用于内部的一些全局容器的实现和更方便的访问TopModel

TopProvider(
  providers: ...,
  child: MaterialApp(
    ...
  ),
),

TopModel

  • 全局获取 在MoLc中,顶级Mode被命名为TopModel,继承于Model,在上述TopProvider里注册。你几乎可以在任意地方通过顶级函数top<T>()来获取你需要的TopModel,而不需要传入context
  • 局部刷新 在实际项目中顶级Model带来了另一个问题,顶级Model的刷新会导致整个App的build,也就是性能问题。为了解决这个问题,MoLc定义了一个名为EventModel<T>Mixin,T为Event的类型。

你可以给为你的TopModel定义一个enum的Event:

enum TestEvent { event1, event2, event3 }

然后TopModel混入EventModel<T>

class TestTopModel extends TopModel with EventModel<TestEvent> {}

需要监听的Model混入EventConsumerMixin

class Test2Model extends Model with EventConsumerForModel {}

Model监听Event

model.listenTopModelEvent(TestEvent.event4);

TopModel发送Event

testTopModel.refreshEvent(TestEvent.event4)

这样就完成的TopModel对于需要监听部分事件Model的局部刷新。

ExposedMixin

上面提到过InheritedWidget的特性,只能实现子context对于父context的数据访问。而实际在业务中,我们常常会遇到父context访问子context的需求,或者需要兄弟context相互访问。MoLc也提供了这样的实现,只需要混入一个ExposedMixin

Model混入ExposedMixin

class Test3Model extends Model with ExposedMixin {}

然后你就可以在任意地方通过顶级函数find<T>()获取到这个Model,如果这个Model当前不存在或者已经disposed是获取不到的。

Logic混入ExposedMixin

class Test3Logic extends Logic with ExposedMixin {}

此时的Logic你可以在任意地方进行调用,轻松的实现了Logic的复用。这里有一个TODO:未注册在BuildContext中的Logic的调用,目前只能调用当前APP的BuildContext树中存在过的Logic

Mutable

最后说一下Mutable,来源于android的mutableStateOfGetX,可以和做到GetX一样的刷新粒度。 先看使用:

final abc = 100.mt;
MutableWidget(
  (context) => Row(
    children: [
      Text(abc.value.toString()),
      TextButton(
          onPressed: () {
            abc.value += 1;
          },
          child: Text('+1')),
    ],
  ),
),

如示例,.mtint包装为Mutable<int>,给abc赋值的时候,依赖abc值的Text会自动刷新。MutableWidget支持嵌套使用,支持多个widget监听同一个Mutable值。

最后放上pub链接,感兴趣可以尝试一下。