MoLc是我基于Provider封装的一个状态管理的库。克服了一些Provider的不足,吸取了GetX和RiverPod的一些特性,实现了数据逻辑分离、状态共享、逻辑复用、精准刷新、颗粒刷新等特性。
先介绍使用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之前要了解Model和Logic。
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中获取上层context的Model用context.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的核心组件是 ModelWidget、LogicWidget、MoLcWidget,前两个适合只有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的mutableStateOf和GetX,可以和做到GetX一样的刷新粒度。
先看使用:
final abc = 100.mt;
MutableWidget(
(context) => Row(
children: [
Text(abc.value.toString()),
TextButton(
onPressed: () {
abc.value += 1;
},
child: Text('+1')),
],
),
),
如示例,.mt将int包装为Mutable<int>,给abc赋值的时候,依赖abc值的Text会自动刷新。MutableWidget支持嵌套使用,支持多个widget监听同一个Mutable值。
最后放上pub链接,感兴趣可以尝试一下。